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

572 lines
24 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 } from 'react'
import { ClickableTextV2 } from '@/components/ClickableTextV2'
export default function DemoV2Page() {
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 [usageCount, setUsageCount] = useState(0)
const [isPremium] = useState(false)
// 模擬分析後的句子資料(新版本)
const mockSentenceAnalysis = {
meaning: "他在我們的會議中提出了這件事,但沒有人同意。這句話表達了在會議中有人提出某個議題或想法,但得不到其他與會者的認同。",
highValueWords: ["brought", "up", "meeting", "agreed"], // 高價值詞彙
phrasesDetected: [
{
phrase: "bring up",
words: ["brought", "up"],
colorCode: "#F59E0B"
}
],
words: {
"he": {
word: "he",
translation: "他",
definition: "Used to refer to a male person or animal",
partOfSpeech: "pronoun",
pronunciation: "/hiː/",
synonyms: ["him", "that man"],
antonyms: [],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"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",
costIncurred: 0 // 高價值免費
},
"this": {
word: "this",
translation: "這個",
definition: "Used to indicate something near or just mentioned",
partOfSpeech: "pronoun",
pronunciation: "/ðɪs/",
synonyms: ["that", "it"],
antonyms: [],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"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
},
"up": {
word: "up",
translation: "向上",
definition: "Toward a higher place or position",
partOfSpeech: "adverb",
pronunciation: "/ʌp/",
synonyms: ["upward", "above"],
antonyms: ["down", "below"],
isPhrase: true,
isHighValue: true, // 高價值片語部分
learningPriority: "high",
phraseInfo: {
phrase: "bring up",
meaning: "提出(話題)、養育",
warning: "\"up\" 在這裡是片語 \"bring up\" 的一部分,不是單獨的\"向上\"的意思",
colorCode: "#F59E0B"
},
difficultyLevel: "B1",
costIncurred: 0 // 高價值免費
},
"during": {
word: "during",
translation: "在...期間",
definition: "Throughout the course or duration of",
partOfSpeech: "preposition",
pronunciation: "/ˈdjʊərɪŋ/",
synonyms: ["throughout", "while"],
antonyms: [],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A2",
costIncurred: 1
},
"our": {
word: "our",
translation: "我們的",
definition: "Belonging to us",
partOfSpeech: "pronoun",
pronunciation: "/aʊər/",
synonyms: ["ours"],
antonyms: [],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"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, // 高價值單字B2級
learningPriority: "high",
difficultyLevel: "B2",
costIncurred: 0 // 高價值免費
},
"and": {
word: "and",
translation: "和、而且",
definition: "Used to connect words or clauses",
partOfSpeech: "conjunction",
pronunciation: "/ænd/",
synonyms: ["plus", "also"],
antonyms: [],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"no": {
word: "no",
translation: "沒有",
definition: "Not any; not one",
partOfSpeech: "determiner",
pronunciation: "/nəʊ/",
synonyms: ["none", "zero"],
antonyms: ["some", "any"],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"one": {
word: "one",
translation: "一個人、任何人",
definition: "A single person or thing",
partOfSpeech: "pronoun",
pronunciation: "/wʌn/",
synonyms: ["someone", "anybody"],
antonyms: ["none", "nobody"],
isPhrase: false,
isHighValue: false, // 低價值詞彙
learningPriority: "low",
difficultyLevel: "A1",
costIncurred: 1
},
"agreed": {
word: "agreed",
translation: "同意",
definition: "Past tense of agree; to have the same opinion",
partOfSpeech: "verb",
pronunciation: "/əˈɡriːd/",
synonyms: ["consented", "accepted", "approved"],
antonyms: ["disagreed", "refused"],
isPhrase: false,
isHighValue: true, // 高價值單字B1級
learningPriority: "medium",
difficultyLevel: "B1",
costIncurred: 0 // 高價值免費
}
}
}
// 處理句子分析
const handleAnalyzeSentence = async () => {
if (!textInput.trim()) return
// 檢查使用次數限制
if (!isPremium && usageCount >= 5) {
alert('❌ 免費用戶 3 小時內只能分析 5 次句子,請稍後再試或升級到付費版本')
return
}
setIsAnalyzing(true)
try {
// 模擬 API 調用
await new Promise(resolve => setTimeout(resolve, 2000))
setSentenceAnalysis(mockSentenceAnalysis.words)
setSentenceMeaning(mockSentenceAnalysis.meaning)
setShowAnalysisView(true)
setUsageCount(prev => prev + 1) // 句子分析扣除1次
} catch (error) {
console.error('Error analyzing sentence:', error)
alert('分析句子時發生錯誤,請稍後再試')
} finally {
setIsAnalyzing(false)
}
}
const getHighValueCount = () => {
if (!sentenceAnalysis) return 0
return Object.values(sentenceAnalysis).filter((word: any) => word.isHighValue).length
}
const getLowValueCount = () => {
if (!sentenceAnalysis) return 0
return Object.values(sentenceAnalysis).filter((word: any) => !word.isHighValue).length
}
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"> v2.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-blue-50 to-purple-50 rounded-xl p-6 mb-6 border border-blue-200">
<h2 className="text-lg font-semibold mb-3 text-blue-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">
<div className="w-4 h-4 bg-yellow-400 border-2 border-yellow-500 rounded"></div>
<span><strong></strong> - </span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-400 border-2 border-green-500 rounded"></div>
<span><strong></strong> - </span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<div className="w-4 h-4 border-b-2 border-blue-400"></div>
<span><strong></strong> - 1 </span>
</div>
<div className="flex items-center gap-2">
<span className="text-lg"></span>
<span><strong></strong> - </span>
</div>
</div>
</div>
</div>
{/* Input Mode Selection */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"></h2>
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => setMode('manual')}
className={`p-4 rounded-lg border-2 transition-all ${
mode === 'manual'
? 'border-blue-600 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<div className="text-2xl mb-2"></div>
<div className="font-semibold"></div>
<div className="text-sm text-gray-600 mt-1"></div>
</button>
<button
onClick={() => setMode('screenshot')}
disabled={!isPremium}
className={`p-4 rounded-lg border-2 transition-all relative ${
mode === 'screenshot'
? 'border-blue-600 bg-blue-50'
: isPremium
? 'border-gray-200 hover:border-gray-300'
: 'border-gray-200 bg-gray-100 cursor-not-allowed opacity-60'
}`}
>
<div className="text-2xl mb-2">📷</div>
<div className="font-semibold"></div>
<div className="text-sm text-gray-600 mt-1"> (Phase 2)</div>
{!isPremium && (
<div className="absolute top-2 right-2 px-2 py-1 bg-yellow-100 text-yellow-700 text-xs rounded-full">
</div>
)}
</button>
</div>
</div>
{/* Content Input */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<div>
<h2 className="text-lg font-semibold mb-4"></h2>
<textarea
value={textInput}
onChange={(e) => {
const value = e.target.value
if (mode === 'manual' && value.length > 50) {
return // 阻止輸入超過50字
}
setTextInput(value)
}}
placeholder={mode === 'manual'
? "輸入英文句子最多50字..."
: "貼上您想要學習的英文文本,例如影劇對話、文章段落..."
}
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 >= 45 ? 'border-yellow-400' :
mode === 'manual' && textInput.length >= 50 ? 'border-red-400' : 'border-gray-300'
}`}
/>
<div className="mt-2 flex justify-between text-sm">
<span className={`${
mode === 'manual' && textInput.length >= 45 ? 'text-yellow-600' :
mode === 'manual' && textInput.length >= 50 ? 'text-red-600' : 'text-gray-600'
}`}>
{mode === 'manual' ? `最多 50 字元 • 目前:${textInput.length} 字元` : `最多 5000 字元 • 目前:${textInput.length} 字元`}
</span>
{mode === 'manual' && textInput.length > 40 && (
<span className={textInput.length >= 50 ? 'text-red-600' : 'text-yellow-600'}>
{textInput.length >= 50 ? '已達上限!' : `還可輸入 ${50 - textInput.length} 字元`}
</span>
)}
</div>
{/* 預設示例 */}
{!textInput && (
<div className="mt-4 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-blue-600 hover:text-blue-800 bg-blue-50 px-3 py-1 rounded border border-blue-200"
>
使He brought this thing up during our meeting and no one agreed.
</button>
</div>
)}
</div>
</div>
{/* 新的按鈕區域 */}
<div className="space-y-4">
{/* 分析句子按鈕 */}
<button
onClick={handleAnalyzeSentence}
disabled={isAnalyzing || (mode === 'manual' && (!textInput || textInput.length > 50)) || (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>
{/* 分析統計 */}
<div className="bg-white rounded-xl shadow-sm p-6 mb-6">
<h2 className="text-lg font-semibold mb-4"></h2>
<div className="grid grid-cols-3 gap-4">
<div className="text-center p-3 bg-green-50 rounded-lg">
<div className="text-2xl font-bold text-green-600">{getHighValueCount()}</div>
<div className="text-sm text-green-700"></div>
<div className="text-xs text-green-600"> </div>
</div>
<div className="text-center p-3 bg-blue-50 rounded-lg">
<div className="text-2xl font-bold text-blue-600">{getLowValueCount()}</div>
<div className="text-sm text-blue-700"></div>
<div className="text-xs text-blue-600">💰 </div>
</div>
<div className="text-center p-3 bg-gray-50 rounded-lg">
<div className="text-2xl font-bold text-gray-600">1</div>
<div className="text-sm text-gray-700"></div>
<div className="text-xs text-gray-600">🔍 </div>
</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="bg-gray-50 p-4 rounded-lg mb-4">
<div className="text-lg leading-relaxed">
{textInput}
</div>
</div>
<h3 className="text-base font-semibold mb-2"></h3>
<div className="text-gray-700 leading-relaxed">
{sentenceMeaning}
</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>💡 </strong>AI
</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">brought</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">meeting</div>
<span></span>
</div>
<div className="flex items-center gap-2">
<div className="px-2 py-1 border-b border-blue-300">thing</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={textInput}
analysis={sentenceAnalysis}
highValueWords={mockSentenceAnalysis.highValueWords}
phrasesDetected={mockSentenceAnalysis.phrasesDetected}
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 mb-6">
<h2 className="text-lg font-semibold mb-4">使</h2>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-gray-600"></div>
<div className="text-2xl font-bold text-blue-600">{usageCount}</div>
<div className="text-xs text-gray-500"> 1 </div>
</div>
<div>
<div className="text-sm text-gray-600"></div>
<div className={`text-2xl font-bold ${5 - usageCount <= 1 ? 'text-red-600' : 'text-green-600'}`}>
{5 - usageCount}
</div>
<div className="text-xs text-gray-500">
{isPremium ? '無限制' : '3小時內重置'}
</div>
</div>
</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>
)
}