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

500 lines
21 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 } 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 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
}
}
}
// 處理句子分析
const handleAnalyzeSentence = async () => {
if (!textInput.trim()) return
if (!isPremium && usageCount >= 5) {
alert('❌ 免費用戶 3 小時內只能分析 5 次句子,請稍後再試或升級到付費版本')
return
}
setIsAnalyzing(true)
try {
await new Promise(resolve => setTimeout(resolve, 2500))
// 根據輸入文本決定使用哪個模擬資料
const hasGrammarErrors = textInput.toLowerCase().includes('go to school yesterday') ||
textInput.toLowerCase().includes('meet my friends')
if (hasGrammarErrors) {
setSentenceAnalysis(mockErrorSentenceAnalysis.words)
setSentenceMeaning(mockErrorSentenceAnalysis.meaning)
setGrammarCorrection(mockErrorSentenceAnalysis.grammarCorrection)
setFinalText(mockErrorSentenceAnalysis.grammarCorrection.correctedText || textInput)
} else {
setSentenceAnalysis(mockCorrectSentenceAnalysis.words)
setSentenceMeaning(mockCorrectSentenceAnalysis.meaning)
setGrammarCorrection(mockCorrectSentenceAnalysis.grammarCorrection)
setFinalText(textInput)
}
setShowAnalysisView(true)
setUsageCount(prev => prev + 1)
} catch (error) {
console.error('Error analyzing sentence:', error)
alert('分析句子時發生錯誤,請稍後再試')
} finally {
setIsAnalyzing(false)
}
}
const handleAcceptCorrection = () => {
if (grammarCorrection?.correctedText) {
setFinalText(grammarCorrection.correctedText)
// 這裡可以重新分析修正後的句子
alert('✅ 已採用修正版本,後續學習將基於正確的句子進行!')
}
}
const handleRejectCorrection = () => {
setFinalText(grammarCorrection?.originalText || textInput)
alert('📝 已保持原始版本,將基於您的原始輸入進行學習。')
}
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-gray-600"> v3.0</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>
{/* 功能說明 */}
<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>
)
}