dramaling-vocab-learning/frontend/app/demo-v3/page.tsx

594 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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, useEffect } from 'react'
import { ClickableTextV2 } from '@/components/ClickableTextV2'
import { GrammarCorrectionPanel } from '@/components/GrammarCorrectionPanel'
export default function DemoV3Page() {
const [mode, setMode] = useState<'manual' | 'screenshot'>('manual')
const [textInput, setTextInput] = useState('')
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [showAnalysisView, setShowAnalysisView] = useState(false)
const [sentenceAnalysis, setSentenceAnalysis] = useState<any>(null)
const [sentenceMeaning, setSentenceMeaning] = useState('')
const [grammarCorrection, setGrammarCorrection] = useState<any>(null)
const [finalText, setFinalText] = useState('') // 最終用於分析的文本
const [usageCount, setUsageCount] = useState(0)
const [isPremium] = useState(false)
const [apiConnected, setApiConnected] = useState(false)
// 模擬正確句子的分析資料
const mockCorrectSentenceAnalysis = {
meaning: "他在我們的會議中提出了這件事,但沒有人同意。這句話表達了在會議中有人提出某個議題或想法,但得不到其他與會者的認同。",
grammarCorrection: {
hasErrors: false,
originalText: "He brought this thing up during our meeting and no one agreed.",
correctedText: null,
corrections: [],
confidenceScore: 0.98
},
highValueWords: ["brought", "up", "meeting", "agreed"],
words: {
"brought": {
word: "brought",
translation: "帶來、提出",
definition: "Past tense of bring; to take or carry something to a place",
partOfSpeech: "verb",
pronunciation: "/brɔːt/",
synonyms: ["carried", "took", "delivered"],
antonyms: ["removed", "took away"],
isPhrase: true,
isHighValue: true,
learningPriority: "high",
phraseInfo: {
phrase: "bring up",
meaning: "提出(話題)、養育",
warning: "在這個句子中,\"brought up\" 是一個片語,意思是\"提出話題\",而不是單純的\"帶來\"",
colorCode: "#F59E0B"
},
difficultyLevel: "B1"
},
"meeting": {
word: "meeting",
translation: "會議",
definition: "An organized gathering of people for discussion",
partOfSpeech: "noun",
pronunciation: "/ˈmiːtɪŋ/",
synonyms: ["conference", "assembly", "gathering"],
antonyms: [],
isPhrase: false,
isHighValue: true,
learningPriority: "high",
difficultyLevel: "B2"
},
"thing": {
word: "thing",
translation: "事情、東西",
definition: "An object, fact, or situation",
partOfSpeech: "noun",
pronunciation: "/θɪŋ/",
synonyms: ["object", "matter", "item"],
antonyms: [],
isPhrase: false,
isHighValue: false,
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
}
}
}
// 模擬有語法錯誤的句子分析資料
const mockErrorSentenceAnalysis = {
meaning: "我昨天去學校遇見了我的朋友們。這句話描述了過去發生的事情,表達了去學校並遇到朋友的經歷。",
grammarCorrection: {
hasErrors: true,
originalText: "I go to school yesterday and meet my friends.",
correctedText: "I went to school yesterday and met my friends.",
corrections: [
{
position: { start: 2, end: 4 },
errorType: "tense_mismatch",
original: "go",
corrected: "went",
reason: "過去式時態修正:句子中有 'yesterday',應使用過去式",
severity: "high"
},
{
position: { start: 29, end: 33 },
errorType: "tense_mismatch",
original: "meet",
corrected: "met",
reason: "過去式時態修正:與 'went' 保持時態一致",
severity: "high"
}
],
confidenceScore: 0.95
},
highValueWords: ["went", "yesterday", "met", "friends"],
words: {
"went": {
word: "went",
translation: "去、前往",
definition: "Past tense of go; to move or travel to a place",
partOfSpeech: "verb",
pronunciation: "/went/",
synonyms: ["traveled", "moved", "proceeded"],
antonyms: ["stayed", "remained"],
isPhrase: false,
isHighValue: true,
learningPriority: "high",
difficultyLevel: "A2"
},
"yesterday": {
word: "yesterday",
translation: "昨天",
definition: "The day before today",
partOfSpeech: "adverb",
pronunciation: "/ˈjestədeɪ/",
synonyms: ["the day before"],
antonyms: ["tomorrow", "today"],
isPhrase: false,
isHighValue: true,
learningPriority: "medium",
difficultyLevel: "A1"
},
"met": {
word: "met",
translation: "遇見、認識",
definition: "Past tense of meet; to encounter or come together with",
partOfSpeech: "verb",
pronunciation: "/met/",
synonyms: ["encountered", "saw", "found"],
antonyms: ["avoided", "missed"],
isPhrase: false,
isHighValue: true,
learningPriority: "high",
difficultyLevel: "A2"
},
"friends": {
word: "friends",
translation: "朋友們",
definition: "People you like and know well",
partOfSpeech: "noun",
pronunciation: "/frends/",
synonyms: ["companions", "buddies", "pals"],
antonyms: ["enemies", "strangers"],
isPhrase: false,
isHighValue: true,
learningPriority: "medium",
difficultyLevel: "A1"
},
"school": {
word: "school",
translation: "學校",
definition: "A place where children go to learn",
partOfSpeech: "noun",
pronunciation: "/skuːl/",
synonyms: ["educational institution"],
antonyms: [],
isPhrase: false,
isHighValue: false,
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
}
}
}
// 處理句子分析 - 使用真實API
const handleAnalyzeSentence = async () => {
if (!textInput.trim()) return
if (!isPremium && usageCount >= 5) {
alert('❌ 免費用戶 3 小時內只能分析 5 次句子,請稍後再試或升級到付費版本')
return
}
setIsAnalyzing(true)
try {
console.log('🚀 開始API調用')
console.log('📝 輸入文本:', textInput)
console.log('🌐 API URL:', 'http://localhost:5000/api/ai/analyze-sentence')
// 調用真實的後端API
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputText: textInput,
analysisMode: 'full',
forceRefresh: true // 暫時強制刷新,避免舊快取問題
})
})
console.log('📡 API響應狀態:', response.status, response.statusText)
console.log('📦 響應頭:', [...response.headers.entries()])
if (!response.ok) {
const errorText = await response.text()
console.log('❌ 錯誤響應內容:', errorText)
throw new Error(`API 錯誤: ${response.status} ${response.statusText} - ${errorText}`)
}
const result = await response.json()
console.log('✅ API響應數據:', result)
if (result.success) {
console.log('💫 開始更新前端狀態')
// 確保數據結構完整
const wordAnalysis = result.data.wordAnalysis || {}
const sentenceMeaning = result.data.sentenceMeaning || {}
const grammarCorrection = result.data.grammarCorrection || { hasErrors: false }
const finalText = result.data.finalAnalysisText || textInput
console.log('📊 詞彙分析詞數:', Object.keys(wordAnalysis).length)
console.log('🎯 高價值詞彙:', result.data.highValueWords)
console.log('📝 翻譯內容:', sentenceMeaning.translation)
// 批次更新狀態,避免競態條件
setSentenceAnalysis(wordAnalysis)
setSentenceMeaning((sentenceMeaning.translation || '翻譯處理中...') + ' ' + (sentenceMeaning.explanation || '解釋處理中...'))
setGrammarCorrection(grammarCorrection)
setFinalText(finalText)
// 延遲顯示分析視圖,確保狀態更新完成
setTimeout(() => {
setShowAnalysisView(true)
console.log('✅ 分析視圖已顯示')
}, 100)
setUsageCount(prev => prev + 1)
console.log('🎉 狀態更新完成')
} else {
throw new Error(result.error || '分析失敗')
}
} catch (error) {
console.error('❌ API錯誤詳情:', error)
// 不要自動回退到模擬資料,讓用戶知道真實錯誤
alert(`🔌 無法連接到後端API:\n\n${error instanceof Error ? error.message : '未知錯誤'}\n\n請檢查:\n1. 後端服務是否運行在 localhost:5000\n2. CORS 設定是否正確\n3. 網路連接是否正常`)
// 重置分析狀態
setShowAnalysisView(false)
} finally {
setIsAnalyzing(false)
}
}
const handleAcceptCorrection = () => {
if (grammarCorrection?.correctedText) {
setFinalText(grammarCorrection.correctedText)
// 這裡可以重新分析修正後的句子
alert('✅ 已採用修正版本,後續學習將基於正確的句子進行!')
}
}
const handleRejectCorrection = () => {
setFinalText(grammarCorrection?.originalText || textInput)
alert('📝 已保持原始版本,將基於您的原始輸入進行學習。')
}
// 檢查API連接狀態
useEffect(() => {
const checkApiConnection = async () => {
try {
const response = await fetch('http://localhost:5000/api/ai/test/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ inputText: 'test', extractionType: 'vocabulary', cardCount: 1 })
})
setApiConnected(response.ok)
} catch (error) {
setApiConnected(false)
}
}
checkApiConnection()
}, [])
return (
<div className="min-h-screen bg-gray-50">
{/* Navigation */}
<nav className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center">
<h1 className="text-2xl font-bold text-blue-600">DramaLing</h1>
</div>
<div className="flex items-center space-x-4">
<span className="text-green-600 font-medium">🔗 API整合 v3.0</span>
<span className={`text-xs px-2 py-1 rounded-full ${
apiConnected
? 'bg-green-100 text-green-700'
: 'bg-yellow-100 text-yellow-700'
}`}>
{apiConnected ? '✅ 後端已連接' : '⏳ 檢查中...'}
</span>
</div>
</div>
</div>
</nav>
<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>
{/* API 連接狀態 */}
<div className={`p-4 rounded-lg mb-6 border ${
apiConnected
? 'bg-green-50 border-green-200'
: 'bg-red-50 border-red-200'
}`}>
<div className="flex items-center gap-2">
<span className="text-lg">
{apiConnected ? '✅' : '❌'}
</span>
<span className={`font-medium ${
apiConnected ? 'text-green-800' : 'text-red-800'
}`}>
API : {apiConnected ? '已連接' : '未連接'}
</span>
</div>
{!apiConnected && (
<p className="text-red-700 text-sm mt-2">
http://localhost:5000 運行
</p>
)}
</div>
{/* 功能說明 */}
<div className="bg-gradient-to-r from-red-50 to-green-50 rounded-xl p-6 mb-6 border border-red-200">
<h2 className="text-lg font-semibold mb-3 text-red-800">🔧 + </h2>
<div className="grid md:grid-cols-2 gap-4 text-sm">
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="text-lg"></span>
<span><strong></strong> - 9</span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg">🔧</span>
<span><strong></strong> - </span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<span className="text-lg"></span>
<span><strong></strong> - </span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg">💰</span>
<span><strong></strong> - </span>
</div>
</div>
</div>
</div>
{/* Content Input */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"> (300)</h2>
<textarea
value={textInput}
onChange={(e) => {
const value = e.target.value
if (mode === 'manual' && value.length > 300) {
return // 阻止輸入超過300字
}
setTextInput(value)
}}
placeholder={mode === 'manual'
? "輸入英文句子最多300字..."
: "貼上您想要學習的英文文本,例如影劇對話、文章段落..."
}
className={`w-full h-40 px-4 py-3 border rounded-lg focus:ring-2 focus:ring-blue-600 focus:border-transparent outline-none resize-none ${
mode === 'manual' && textInput.length >= 280 ? 'border-yellow-400' :
mode === 'manual' && textInput.length >= 300 ? 'border-red-400' : 'border-gray-300'
}`}
/>
<div className="mt-2 flex justify-between text-sm">
<span className={`${
mode === 'manual' && textInput.length >= 280 ? 'text-yellow-600' :
mode === 'manual' && textInput.length >= 300 ? 'text-red-600' : 'text-gray-600'
}`}>
{mode === 'manual' ? `最多 300 字元 • 目前:${textInput.length} 字元` : `最多 5000 字元 • 目前:${textInput.length} 字元`}
</span>
{mode === 'manual' && textInput.length > 250 && (
<span className={textInput.length >= 300 ? 'text-red-600' : 'text-yellow-600'}>
{textInput.length >= 300 ? '已達上限!' : `還可輸入 ${300 - textInput.length} 字元`}
</span>
)}
</div>
{/* 預設示例 - 包含語法錯誤和正確的句子 */}
{!textInput && (
<div className="mt-4 space-y-3">
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-sm text-gray-700 mb-2">
<strong> </strong>
</div>
<button
onClick={() => setTextInput("He brought this thing up during our meeting and no one agreed.")}
className="text-sm text-green-600 hover:text-green-800 bg-green-50 px-3 py-1 rounded border border-green-200 w-full text-left"
>
He brought this thing up during our meeting and no one agreed.
</button>
</div>
<div className="p-3 bg-red-50 rounded-lg">
<div className="text-sm text-red-700 mb-2">
<strong> </strong>
</div>
<button
onClick={() => setTextInput("I go to school yesterday and meet my friends.")}
className="text-sm text-red-600 hover:text-red-800 bg-red-50 px-3 py-1 rounded border border-red-200 w-full text-left"
>
I go to school yesterday and meet my friends.
</button>
</div>
</div>
)}
</div>
{/* 分析按鈕 */}
<div className="space-y-4">
<button
onClick={handleAnalyzeSentence}
disabled={isAnalyzing || (mode === 'manual' && (!textInput || textInput.length > 300)) || (mode === 'screenshot')}
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>
...
</span>
) : (
'🔍 語法檢查 + 高價值詞彙分析'
)}
</button>
<div className="text-center text-sm text-gray-600">
{isPremium ? (
<span className="text-green-600">🌟 使</span>
) : (
<span className={usageCount >= 4 ? 'text-red-600' : usageCount >= 3 ? 'text-yellow-600' : 'text-gray-600'}>
使 {usageCount}/5 (3)
{usageCount >= 5 && <span className="block text-red-500 mt-1"></span>}
</span>
)}
</div>
</div>
</div>
) : (
/* 句子分析視圖 */
<div className="max-w-4xl mx-auto">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold"> + </h1>
<button
onClick={() => setShowAnalysisView(false)}
className="text-gray-600 hover:text-gray-900"
>
</button>
</div>
{/* 語法修正面板 */}
{grammarCorrection && (
<GrammarCorrectionPanel
correction={grammarCorrection}
onAcceptCorrection={handleAcceptCorrection}
onRejectCorrection={handleRejectCorrection}
/>
)}
{/* 原始句子 vs 分析句子 */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"></h2>
<div className="grid md:grid-cols-2 gap-4">
<div>
<h3 className="text-sm font-medium text-gray-700 mb-2">📝 </h3>
<div className="p-3 bg-gray-50 rounded-lg border">
<div className="text-base">{textInput}</div>
</div>
</div>
<div>
<h3 className="text-sm font-medium text-gray-700 mb-2">🎯 </h3>
<div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
<div className="text-base font-medium">{finalText}</div>
{finalText !== textInput && (
<div className="text-xs text-blue-600 mt-1"> </div>
)}
</div>
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-gray-700 mb-2">📖 </h3>
<div className="text-gray-700 leading-relaxed p-3 bg-gray-50 rounded-lg">
{sentenceMeaning}
</div>
</div>
</div>
{/* 互動式文字 */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"> - </h2>
<div className="p-4 bg-gradient-to-r from-blue-50 to-green-50 rounded-lg border border-blue-200 mb-4">
<p className="text-sm text-blue-800 mb-3">
<strong>💡 {finalText !== textInput ? '修正後' : '原始'}</strong>
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
<div className="flex items-center gap-2">
<div className="px-2 py-1 bg-yellow-100 border-2 border-yellow-400 rounded"></div>
<span></span>
</div>
<div className="flex items-center gap-2">
<div className="px-2 py-1 bg-green-100 border-2 border-green-400 rounded"></div>
<span></span>
</div>
<div className="flex items-center gap-2">
<div className="px-2 py-1 border-b border-blue-300"></div>
<span>1</span>
</div>
</div>
</div>
<div className="p-6 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300">
<ClickableTextV2
text={finalText}
analysis={sentenceAnalysis}
remainingUsage={5 - usageCount}
onWordClick={(word, analysis) => {
console.log('Clicked word:', word, analysis)
}}
onWordCostConfirm={async (word, cost) => {
if (usageCount >= 5) {
alert('❌ 使用額度不足,無法查詢低價值詞彙')
return false
}
const confirmed = window.confirm(
`查詢 "${word}" 將消耗 ${cost} 次使用額度,您剩餘 ${5 - usageCount} 次。\n\n是否繼續`
)
if (confirmed) {
setUsageCount(prev => prev + cost)
return true
}
return false
}}
/>
</div>
</div>
{/* 操作按鈕 */}
<div className="bg-white rounded-xl shadow-sm p-6">
<div className="flex gap-4">
<button
onClick={() => setShowAnalysisView(false)}
className="flex-1 bg-gray-200 text-gray-700 py-3 rounded-lg font-medium hover:bg-gray-300 transition-colors"
>
🔄
</button>
<button
onClick={() => alert('詞卡生成功能開發中...')}
className="flex-1 bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
📖
</button>
</div>
</div>
</div>
)}
</div>
</div>
)
}