dramaling-vocab-learning/frontend/app/generate/page.tsx

800 lines
32 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useMemo, useCallback } from 'react'
import { ProtectedRoute } from '@/components/ProtectedRoute'
import { Navigation } from '@/components/Navigation'
import { ClickableTextV2 } from '@/components/ClickableTextV2'
import { flashcardsService } from '@/lib/services/flashcards'
import Link from 'next/link'
// 常數定義
const CEFR_LEVELS = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] as const
const MAX_MANUAL_INPUT_LENGTH = 300
// 工具函數
const getLevelIndex = (level: string): number => {
return CEFR_LEVELS.indexOf(level as typeof CEFR_LEVELS[number])
}
const getTargetLearningRange = (userLevel: string): string => {
const ranges: Record<string, string> = {
'A1': 'A2-B1', 'A2': 'B1-B2', 'B1': 'B2-C1',
'B2': 'C1-C2', 'C1': 'C2', 'C2': 'C2'
}
return ranges[userLevel] || 'B1-B2'
}
interface GrammarCorrection {
hasErrors: boolean;
originalText: string;
correctedText: string;
corrections: Array<{
error: string;
correction: string;
type: string;
explanation: string;
}>;
}
interface PhrasePopup {
phrase: string;
analysis: any;
position: { x: number; y: number };
}
function GenerateContent() {
const [textInput, setTextInput] = useState('')
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [showAnalysisView, setShowAnalysisView] = useState(false)
const [sentenceAnalysis, setSentenceAnalysis] = useState<Record<string, any> | null>(null)
const [sentenceMeaning, setSentenceMeaning] = useState('')
const [grammarCorrection, setGrammarCorrection] = useState<GrammarCorrection | null>(null)
const [finalText, setFinalText] = useState('')
const [phrasePopup, setPhrasePopup] = useState<PhrasePopup | null>(null)
// 處理句子分析 - 使用假資料測試
const handleAnalyzeSentence = async () => {
console.log('🚀 handleAnalyzeSentence 被調用 (假資料模式)')
setIsAnalyzing(true)
try {
// 模擬API延遲
await new Promise(resolve => setTimeout(resolve, 1000))
// 使用有語法錯誤的測試句子
const testSentence = "She just join the team, so let's cut her some slack until she get used to the workflow."
// 假資料:完整詞彙分析結果 (包含句子中的所有詞彙)
const mockAnalysis = {
"she": {
word: "she",
translation: "她",
definition: "female person pronoun",
partOfSpeech: "pronoun",
pronunciation: "/ʃiː/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["her"],
example: "She is a teacher.",
exampleTranslation: "她是一名老師。"
},
"just": {
word: "just",
translation: "剛剛;僅僅",
definition: "recently; only",
partOfSpeech: "adverb",
pronunciation: "/dʒʌst/",
difficultyLevel: "A2",
isPhrase: false,
synonyms: ["recently", "only", "merely"],
example: "I just arrived.",
exampleTranslation: "我剛到。"
},
"join": {
word: "join",
translation: "加入",
definition: "to become a member of",
partOfSpeech: "verb",
pronunciation: "/dʒɔɪn/",
difficultyLevel: "B1",
isPhrase: false,
synonyms: ["enter", "become part of"],
example: "I want to join the team.",
exampleTranslation: "我想加入團隊。"
},
"the": {
word: "the",
translation: "定冠詞",
definition: "definite article",
partOfSpeech: "article",
pronunciation: "/ðə/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: [],
example: "The cat is sleeping.",
exampleTranslation: "貓在睡覺。"
},
"team": {
word: "team",
translation: "團隊",
definition: "a group of people working together",
partOfSpeech: "noun",
pronunciation: "/tiːm/",
difficultyLevel: "A2",
isPhrase: false,
synonyms: ["group", "crew"],
example: "Our team works well together.",
exampleTranslation: "我們的團隊合作得很好。"
},
"so": {
word: "so",
translation: "所以;如此",
definition: "therefore; to such a degree",
partOfSpeech: "adverb",
pronunciation: "/soʊ/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["therefore", "thus"],
example: "It was raining, so I stayed home.",
exampleTranslation: "下雨了,所以我待在家裡。"
},
"let's": {
word: "let's",
translation: "讓我們",
definition: "let us (contraction)",
partOfSpeech: "contraction",
pronunciation: "/lets/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["let us"],
example: "Let's go to the park.",
exampleTranslation: "我們去公園吧。"
},
"cut": {
word: "cut",
translation: "切;削減",
definition: "to use a knife or other sharp tool to divide something",
partOfSpeech: "verb",
pronunciation: "/kʌt/",
difficultyLevel: "A2",
isPhrase: false,
synonyms: ["slice", "chop", "reduce"],
example: "Please cut the apple.",
exampleTranslation: "請切蘋果。"
},
"her": {
word: "her",
translation: "她的;她",
definition: "belonging to or associated with a female",
partOfSpeech: "pronoun",
pronunciation: "/hər/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["hers"],
example: "This is her book.",
exampleTranslation: "這是她的書。"
},
"some": {
word: "some",
translation: "一些",
definition: "an unspecified amount or number of",
partOfSpeech: "determiner",
pronunciation: "/sʌm/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["several", "a few"],
example: "I need some help.",
exampleTranslation: "我需要一些幫助。"
},
"slack": {
word: "slack",
translation: "寬鬆;懈怠",
definition: "looseness; lack of tension",
partOfSpeech: "noun",
pronunciation: "/slæk/",
difficultyLevel: "B1",
isPhrase: false,
synonyms: ["looseness", "leeway"],
example: "There's too much slack in this rope.",
exampleTranslation: "這條繩子太鬆了。"
},
"until": {
word: "until",
translation: "直到",
definition: "up to a particular time",
partOfSpeech: "preposition",
pronunciation: "/ʌnˈtɪl/",
difficultyLevel: "A2",
isPhrase: false,
synonyms: ["till", "up to"],
example: "Wait until tomorrow.",
exampleTranslation: "等到明天。"
},
"get": {
word: "get",
translation: "變得;獲得",
definition: "to become or obtain",
partOfSpeech: "verb",
pronunciation: "/ɡet/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["become", "obtain"],
example: "I get tired easily.",
exampleTranslation: "我很容易累。"
},
"used": {
word: "used",
translation: "習慣的",
definition: "familiar with something (used to)",
partOfSpeech: "adjective",
pronunciation: "/juːzd/",
difficultyLevel: "A2",
isPhrase: false,
synonyms: ["accustomed", "familiar"],
example: "I'm not used to this weather.",
exampleTranslation: "我不習慣這種天氣。"
},
"to": {
word: "to",
translation: "到;向",
definition: "preposition expressing direction",
partOfSpeech: "preposition",
pronunciation: "/tu/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: [],
example: "I'm going to school.",
exampleTranslation: "我要去學校。"
},
"workflow": {
word: "workflow",
translation: "工作流程",
definition: "the sequence of processes through which work passes",
partOfSpeech: "noun",
pronunciation: "/ˈːrkfloʊ/",
difficultyLevel: "B2",
isPhrase: false,
synonyms: ["process", "procedure", "system"],
example: "We need to improve our workflow.",
exampleTranslation: "我們需要改善工作流程。"
},
"joined": {
word: "joined",
translation: "加入",
definition: "became a member of (past tense of join)",
partOfSpeech: "verb",
pronunciation: "/dʒɔɪnd/",
difficultyLevel: "B1",
isPhrase: false,
synonyms: ["entered", "became part of"],
example: "He joined the company last year.",
exampleTranslation: "他去年加入了這家公司。"
},
"gets": {
word: "gets",
translation: "變得;獲得",
definition: "becomes or obtains (third person singular)",
partOfSpeech: "verb",
pronunciation: "/ɡets/",
difficultyLevel: "A1",
isPhrase: false,
synonyms: ["becomes", "obtains"],
example: "It gets cold at night.",
exampleTranslation: "晚上會變冷。"
},
"cut someone some slack": {
word: "cut someone some slack",
translation: "對某人寬容一點",
definition: "to be more lenient or forgiving with someone",
partOfSpeech: "idiom",
pronunciation: "/kʌt ˈsʌmwʌn sʌm slæk/",
difficultyLevel: "B2",
isPhrase: true,
synonyms: ["be lenient", "be forgiving", "give leeway"],
example: "Cut him some slack, he's new here.",
exampleTranslation: "對他寬容一點,他是新來的。"
},
}
// 設定結果 - 包含語法錯誤情境
setFinalText("She just joined the team, so let's cut her some slack until she gets used to the workflow.") // 修正後的句子
setSentenceAnalysis(mockAnalysis)
setSentenceMeaning("她剛加入團隊,所以讓我們對她寬容一點,直到她習慣工作流程。")
setGrammarCorrection({
hasErrors: true,
originalText: testSentence, // 有錯誤的原始句子
correctedText: "She just joined the team, so let's cut her some slack until she gets used to the workflow.",
corrections: [
{
error: "join",
correction: "joined",
type: "時態錯誤",
explanation: "第三人稱單數過去式應使用 'joined'"
},
{
error: "get",
correction: "gets",
type: "時態錯誤",
explanation: "第三人稱單數現在式應使用 'gets'"
}
]
})
setShowAnalysisView(true)
console.log('✅ 假資料設定完成')
} catch (error) {
console.error('Error in sentence analysis:', error)
setGrammarCorrection({
hasErrors: true,
originalText: textInput,
correctedText: textInput,
corrections: []
})
setSentenceMeaning('分析過程中發生錯誤,請稍後再試。')
setFinalText(textInput)
setShowAnalysisView(true)
} finally {
setIsAnalyzing(false)
}
}
const handleAcceptCorrection = useCallback(() => {
if (grammarCorrection?.correctedText) {
setFinalText(grammarCorrection.correctedText)
console.log('✅ 已採用修正版本,後續學習將基於正確的句子進行!')
}
}, [grammarCorrection?.correctedText])
const handleRejectCorrection = useCallback(() => {
setFinalText(grammarCorrection?.originalText || textInput)
console.log('📝 已保持原始版本,將基於您的原始輸入進行學習。')
}, [grammarCorrection?.originalText, textInput])
// 詞彙統計計算 - 移到組件頂層避免Hooks順序問題
const vocabularyStats = useMemo(() => {
if (!sentenceAnalysis) return null
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
let simpleCount = 0
let moderateCount = 0
let difficultCount = 0
let phraseCount = 0
Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => {
const isPhrase = wordData?.isPhrase || wordData?.IsPhrase
const difficultyLevel = wordData?.difficultyLevel || 'A1'
if (isPhrase) {
phraseCount++
} else {
const userIndex = getLevelIndex(userLevel)
const wordIndex = getLevelIndex(difficultyLevel)
if (userIndex > wordIndex) {
simpleCount++
} else if (userIndex === wordIndex) {
moderateCount++
} else {
difficultCount++
}
}
})
return { simpleCount, moderateCount, difficultCount, phraseCount }
}, [sentenceAnalysis])
// 保存單個詞彙
const handleSaveWord = useCallback(async (word: string, analysis: any) => {
try {
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 || 'unknown',
example: `Example sentence with ${word}.` // 提供預設例句
}
const response = await flashcardsService.createFlashcard(cardData)
if (response.success) {
console.log(`✅ 已將「${word}」保存到詞卡!`)
return { success: true }
} else {
throw new Error(response.error || '保存失敗')
}
} catch (error) {
console.error('Save word error:', error)
return { success: false, error: error instanceof Error ? error.message : '保存失敗' }
}
}, [])
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{!showAnalysisView ? (
<div className="max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-8">AI </h1>
{/* Content Input */}
<div className="bg-white rounded-xl shadow-sm p-4 sm:p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"></h2>
<textarea
value={textInput}
onChange={(e) => {
const value = e.target.value
if (value.length > MAX_MANUAL_INPUT_LENGTH) {
return
}
setTextInput(value)
}}
placeholder={`輸入英文句子(最多${MAX_MANUAL_INPUT_LENGTH}字)...`}
className={`w-full h-32 sm:h-40 px-3 sm:px-4 py-2 sm:py-3 text-sm sm:text-base border rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none resize-none ${
textInput.length >= MAX_MANUAL_INPUT_LENGTH - 20 ? 'border-yellow-400' :
textInput.length >= MAX_MANUAL_INPUT_LENGTH ? 'border-red-400' : 'border-gray-300'
}`}
/>
<div className="mt-2 flex justify-between text-sm">
<span className={`${
textInput.length >= MAX_MANUAL_INPUT_LENGTH - 20 ? 'text-yellow-600' :
textInput.length >= MAX_MANUAL_INPUT_LENGTH ? 'text-red-600' : 'text-gray-600'
}`}>
{MAX_MANUAL_INPUT_LENGTH} {textInput.length}
</span>
{textInput.length > MAX_MANUAL_INPUT_LENGTH - 50 && (
<span className={textInput.length >= MAX_MANUAL_INPUT_LENGTH ? 'text-red-600' : 'text-yellow-600'}>
{textInput.length >= MAX_MANUAL_INPUT_LENGTH ? '已達上限!' : `還可輸入 ${MAX_MANUAL_INPUT_LENGTH - textInput.length} 字元`}
</span>
)}
</div>
</div>
{/* Action Buttons */}
<div className="space-y-4">
{/* 句子分析按鈕 */}
<button
onClick={handleAnalyzeSentence}
disabled={isAnalyzing || !textInput || textInput.length > MAX_MANUAL_INPUT_LENGTH}
className="w-full bg-blue-600 text-white py-4 rounded-lg font-semibold hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isAnalyzing ? (
<span className="flex items-center justify-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
... (AI 3-5 )
</span>
) : (
'🔍 分析句子'
)}
</button>
{/* 個人化程度指示器 */}
<div className="text-center text-sm text-gray-600 mt-2">
{(() => {
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2'
return (
<div className="flex items-center justify-center gap-2">
<span>🎯 : {userLevel}</span>
<span className="text-gray-400">|</span>
<span>📈 : {getTargetLearningRange(userLevel)}</span>
<Link
href="/settings"
className="text-blue-500 hover:text-blue-700 ml-2"
>
調
</Link>
</div>
)
})()}
</div>
{/* 學習提示系統 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mt-4">
<h3 className="text-sm font-semibold text-blue-900 mb-2">💡 </h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 text-xs">
<div className="flex items-center gap-2">
<span className="inline-block w-4 h-4 bg-gray-50 border border-dashed border-gray-300 rounded opacity-80"></span>
<span className="text-gray-600"> - </span>
</div>
<div className="flex items-center gap-2">
<span className="inline-block w-4 h-4 bg-green-50 border border-green-200 rounded"></span>
<span className="text-gray-600"> - </span>
</div>
<div className="flex items-center gap-2">
<span className="inline-block w-4 h-4 bg-orange-50 border border-orange-200 rounded"></span>
<span className="text-gray-600"> - </span>
</div>
</div>
<p className="text-xs text-blue-700 mt-2">
</p>
</div>
</div>
</div>
) : (
/* 重新設計的句子分析視圖 - 簡潔流暢 */
<div className="max-w-4xl mx-auto">
{/* 移除冗餘標題,直接進入內容 */}
{/* 語法修正面板 - 如果需要的話 */}
{grammarCorrection && grammarCorrection.hasErrors && (
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-6 mb-6">
<div className="flex items-start gap-3">
<div className="text-yellow-600 text-2xl"></div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-yellow-800 mb-2"></h3>
<p className="text-yellow-700 mb-4">AI建議修正以下內容</p>
<div className="space-y-3 mb-4">
<div>
<span className="text-sm font-medium text-yellow-700"></span>
<div className="bg-white p-3 rounded border border-yellow-300 mt-1">
{textInput}
</div>
</div>
<div>
<span className="text-sm font-medium text-yellow-700"></span>
<div className="bg-yellow-100 p-3 rounded border border-yellow-300 mt-1 font-medium">
{grammarCorrection.correctedText || finalText}
</div>
</div>
</div>
<div className="flex gap-3">
<button
onClick={handleAcceptCorrection}
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
</button>
<button
onClick={handleRejectCorrection}
className="px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
📝
</button>
</div>
</div>
</div>
</div>
)}
{/* 主句子展示 - 最重要的內容 */}
<div className="bg-white rounded-xl shadow-lg p-8 mb-6">
{/* 詞彙統計卡片區 */}
{vocabularyStats && (
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4 mb-6">
{/* 簡單詞彙卡片 */}
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-gray-600 mb-1">{vocabularyStats.simpleCount}</div>
<div className="text-gray-600 text-xs sm:text-sm font-medium"></div>
</div>
{/* 適中詞彙卡片 */}
<div className="bg-green-50 border border-green-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-green-700 mb-1">{vocabularyStats.moderateCount}</div>
<div className="text-green-700 text-xs sm:text-sm font-medium"></div>
</div>
{/* 艱難詞彙卡片 */}
<div className="bg-orange-50 border border-orange-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-orange-700 mb-1">{vocabularyStats.difficultCount}</div>
<div className="text-orange-700 text-xs sm:text-sm font-medium"></div>
</div>
{/* 片語與俚語卡片 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 sm:p-4 text-center">
<div className="text-xl sm:text-2xl font-bold text-blue-700 mb-1">{vocabularyStats.phraseCount}</div>
<div className="text-blue-700 text-xs sm:text-sm font-medium"></div>
</div>
</div>
)}
{/* 句子主體展示 */}
<div className="text-left mb-8">
<div className="text-xl sm:text-2xl lg:text-3xl font-medium text-gray-900 mb-6" >
<ClickableTextV2
text={finalText}
analysis={sentenceAnalysis || undefined}
showPhrasesInline={false}
onWordClick={(word, analysis) => {
console.log('Clicked word:', word, analysis)
}}
onSaveWord={handleSaveWord}
/>
</div>
{/* 翻譯 - 參考翻卡背面設計 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<p className="text-gray-700 text-left">{sentenceMeaning}</p>
</div>
{/* 片語和慣用語展示區 */}
{(() => {
if (!sentenceAnalysis) return null
// 提取片語
const phrases: Array<{
phrase: 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,
meaning: wordData?.translation || '',
difficultyLevel: wordData?.difficultyLevel || 'A1'
})
}
})
if (phrases.length === 0) return null
return (
<div className="bg-gray-50 rounded-lg p-4 mt-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<div className="flex flex-wrap gap-2">
{phrases.map((phrase, index) => (
<span
key={index}
className="cursor-pointer transition-all duration-200 rounded-lg relative mx-0.5 px-1 py-0.5 inline-flex items-center gap-1 bg-blue-50 border border-blue-200 hover:bg-blue-100 hover:shadow-lg transform hover:-translate-y-0.5 text-blue-700 font-medium"
onClick={(e) => {
// 找到片語的完整分析資料
const phraseAnalysis = sentenceAnalysis?.["cut someone some slack"]
if (phraseAnalysis) {
// 設定片語彈窗狀態
setPhrasePopup({
phrase: phrase.phrase,
analysis: phraseAnalysis,
position: {
x: e.currentTarget.getBoundingClientRect().left + e.currentTarget.getBoundingClientRect().width / 2,
y: e.currentTarget.getBoundingClientRect().bottom + 10
}
})
}
}}
title={`${phrase.phrase}: ${phrase.meaning}`}
>
{phrase.phrase}
</span>
))}
</div>
</div>
)
})()}
</div>
</div>
{/* 下方操作區 - 簡化 */}
<div className="flex justify-center px-4">
<button
onClick={() => setShowAnalysisView(false)}
className="w-full sm:w-auto px-6 sm:px-8 py-3 bg-primary text-white rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
>
<span></span>
</button>
</div>
</div>
)}
{/* 片語彈窗 */}
{phrasePopup && (
<>
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40"
onClick={() => setPhrasePopup(null)}
/>
<div
className="fixed z-50 bg-white rounded-xl shadow-lg w-96 max-w-md overflow-hidden"
style={{
left: `${phrasePopup.position.x}px`,
top: `${phrasePopup.position.y}px`,
transform: 'translate(-50%, 8px)',
maxHeight: '85vh',
overflowY: 'auto'
}}
>
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 border-b border-blue-200">
<div className="flex justify-end mb-3">
<button
onClick={() => setPhrasePopup(null)}
className="text-gray-400 hover:text-gray-600 w-6 h-6 rounded-full bg-white bg-opacity-80 hover:bg-opacity-100 transition-all flex items-center justify-center"
>
</button>
</div>
<div className="mb-3">
<h3 className="text-2xl font-bold text-gray-900">{phrasePopup.analysis.word}</h3>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-sm bg-gray-100 text-gray-700 px-3 py-1 rounded-full">
{phrasePopup.analysis.partOfSpeech}
</span>
<span className="text-base text-gray-600">{phrasePopup.analysis.pronunciation}</span>
</div>
<span className="px-3 py-1 rounded-full text-sm font-medium border bg-blue-100 text-blue-700 border-blue-200">
{phrasePopup.analysis.difficultyLevel}
</span>
</div>
</div>
<div className="p-4 space-y-4">
<div className="bg-green-50 rounded-lg p-3 border border-green-200">
<h4 className="font-semibold text-green-900 mb-2 text-left text-sm"></h4>
<p className="text-green-800 font-medium text-left">{phrasePopup.analysis.translation}</p>
</div>
<div className="bg-gray-50 rounded-lg p-3 border border-gray-200">
<h4 className="font-semibold text-gray-900 mb-2 text-left text-sm"></h4>
<p className="text-gray-700 text-left text-sm leading-relaxed">{phrasePopup.analysis.definition}</p>
</div>
{phrasePopup.analysis.example && (
<div className="bg-blue-50 rounded-lg p-3 border border-blue-200">
<h4 className="font-semibold text-blue-900 mb-2 text-left text-sm"></h4>
<div className="space-y-2">
<p className="text-blue-800 text-left text-sm italic">
"{phrasePopup.analysis.example}"
</p>
<p className="text-blue-700 text-left text-sm">
{phrasePopup.analysis.exampleTranslation}
</p>
</div>
</div>
)}
</div>
<div className="p-4 pt-2">
<button
onClick={async () => {
const result = await handleSaveWord(phrasePopup.phrase, phrasePopup.analysis)
if (result.success) {
setPhrasePopup(null)
} else {
console.error('Save phrase error:', result.error)
}
}}
className="w-full bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2"
>
<span className="font-medium"></span>
</button>
</div>
</div>
</>
)}
</div>
</div>
)
}
export default function GeneratePage() {
return (
<ProtectedRoute>
<GenerateContent />
</ProtectedRoute>
)
}