feat: 完成詞卡儲存功能整合並移除假資料
🎯 主要功能: - 統一前後端API欄位名稱(word/translation/definition) - 移除所有假資料生成函數,改用真實API調用 - 修正FlashcardForm和相關組件的欄位映射 🔧 技術修正: - 前端handleSaveWord函數使用正確的API欄位 - flashcardsService interface與後端API完全匹配 - handleAnalyzeSentence改為調用真實的analyze-sentence API 📝 代碼清理: - 移除generateMockAnalysis函數(~150行代碼) - 移除getRandomTranslation、getRandomPartOfSpeech等工具函數 - 清理所有mock相關的註釋和變數 ✅ 功能驗證: - 後端API正常運行(localhost:5000) - 前端Portal彈窗樣式完美 - 詞卡儲存功能完整可用 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
aca9ec2f7a
commit
b348780eaa
|
|
@ -876,8 +876,9 @@ function FlashcardsContent() {
|
||||||
initialData={editingCard ? {
|
initialData={editingCard ? {
|
||||||
id: editingCard.id,
|
id: editingCard.id,
|
||||||
cardSetId: editingCard.cardSet ? cardSets.find(cs => cs.name === editingCard.cardSet.name)?.id || cardSets[0]?.id : cardSets[0]?.id,
|
cardSetId: editingCard.cardSet ? cardSets.find(cs => cs.name === editingCard.cardSet.name)?.id || cardSets[0]?.id : cardSets[0]?.id,
|
||||||
english: editingCard.word,
|
word: editingCard.word,
|
||||||
chinese: editingCard.translation,
|
translation: editingCard.translation,
|
||||||
|
definition: editingCard.definition,
|
||||||
pronunciation: editingCard.pronunciation,
|
pronunciation: editingCard.pronunciation,
|
||||||
partOfSpeech: editingCard.partOfSpeech,
|
partOfSpeech: editingCard.partOfSpeech,
|
||||||
example: editingCard.example,
|
example: editingCard.example,
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ function GenerateContent() {
|
||||||
const [isPremium] = useState(true)
|
const [isPremium] = useState(true)
|
||||||
|
|
||||||
|
|
||||||
// 處理句子分析 - 使用假數據進行快速測試
|
// 處理句子分析 - 使用真實API
|
||||||
const handleAnalyzeSentence = async () => {
|
const handleAnalyzeSentence = async () => {
|
||||||
console.log('🚀 handleAnalyzeSentence 被調用 (假數據模式)')
|
console.log('🚀 handleAnalyzeSentence 被調用 (真實API模式)')
|
||||||
console.log('📝 輸入文本:', textInput)
|
console.log('📝 輸入文本:', textInput)
|
||||||
|
|
||||||
if (!textInput.trim()) {
|
if (!textInput.trim()) {
|
||||||
|
|
@ -33,181 +33,46 @@ function GenerateContent() {
|
||||||
setIsAnalyzing(true)
|
setIsAnalyzing(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 模擬API延遲
|
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: textInput
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// 生成假的分析數據
|
if (response.ok) {
|
||||||
const mockAnalysis = generateMockAnalysis(textInput)
|
const result = await response.json()
|
||||||
|
console.log('✅ API分析完成:', result)
|
||||||
|
|
||||||
setSentenceAnalysis(mockAnalysis.wordAnalysis)
|
if (result.success) {
|
||||||
setSentenceMeaning(mockAnalysis.sentenceTranslation)
|
setSentenceAnalysis(result.data.wordAnalysis || {})
|
||||||
setGrammarCorrection(mockAnalysis.grammarCorrection)
|
setSentenceMeaning(result.data.sentenceTranslation || '')
|
||||||
setFinalText(mockAnalysis.finalText)
|
setGrammarCorrection(result.data.grammarCorrection || null)
|
||||||
setShowAnalysisView(true)
|
setFinalText(result.data.finalText || textInput)
|
||||||
setUsageCount(prev => prev + 1)
|
setShowAnalysisView(true)
|
||||||
|
setUsageCount(prev => prev + 1)
|
||||||
console.log('✅ 假數據分析完成:', mockAnalysis)
|
} else {
|
||||||
|
throw new Error(result.error || '分析失敗')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`API錯誤: ${response.status}`)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in mock analysis:', error)
|
console.error('Error in real API analysis:', error)
|
||||||
alert(`分析句子時發生錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`)
|
alert(`分析句子時發生錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`)
|
||||||
} finally {
|
} finally {
|
||||||
setIsAnalyzing(false)
|
setIsAnalyzing(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成假的分析數據
|
|
||||||
const generateMockAnalysis = (inputText: string) => {
|
|
||||||
const words = inputText.toLowerCase().split(/\s+/).filter(word =>
|
|
||||||
word.length > 2 && /^[a-z]+$/.test(word.replace(/[.,!?;:]/g, ''))
|
|
||||||
)
|
|
||||||
|
|
||||||
const wordAnalysis: any = {}
|
|
||||||
|
|
||||||
words.forEach((word, index) => {
|
|
||||||
const cleanWord = word.replace(/[.,!?;:]/g, '')
|
|
||||||
const isHighValue = index % 3 === 0 // 每3個詞中有1個高價值
|
|
||||||
const isPhrase = cleanWord.length > 6 // 長詞視為片語
|
|
||||||
|
|
||||||
wordAnalysis[cleanWord] = {
|
|
||||||
word: cleanWord,
|
|
||||||
translation: getRandomTranslation(cleanWord),
|
|
||||||
definition: `Definition of ${cleanWord} - a common English word`,
|
|
||||||
partOfSpeech: getRandomPartOfSpeech(),
|
|
||||||
pronunciation: `/${cleanWord}/`,
|
|
||||||
isHighValue: isHighValue,
|
|
||||||
isPhrase: isPhrase,
|
|
||||||
difficultyLevel: getRandomDifficulty(),
|
|
||||||
synonyms: [getRandomSynonym(cleanWord), getRandomSynonym(cleanWord)],
|
|
||||||
learningPriority: isHighValue ? 'high' : 'medium'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
wordAnalysis,
|
|
||||||
sentenceTranslation: `這是「${inputText}」的中文翻譯。`,
|
|
||||||
grammarCorrection: {
|
|
||||||
hasErrors: Math.random() > 0.7, // 30%機率有語法錯誤
|
|
||||||
correctedText: inputText,
|
|
||||||
originalText: inputText
|
|
||||||
},
|
|
||||||
finalText: inputText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 輔助函數 - 改進翻譯生成
|
|
||||||
const getRandomTranslation = (word: string) => {
|
|
||||||
// 常見英文單字的實際翻譯
|
|
||||||
const commonTranslations: {[key: string]: string} = {
|
|
||||||
'hello': '你好',
|
|
||||||
'how': '如何',
|
|
||||||
'are': '是',
|
|
||||||
'you': '你',
|
|
||||||
'today': '今天',
|
|
||||||
'good': '好的',
|
|
||||||
'morning': '早晨',
|
|
||||||
'evening': '晚上',
|
|
||||||
'thank': '謝謝',
|
|
||||||
'please': '請',
|
|
||||||
'sorry': '抱歉',
|
|
||||||
'love': '愛',
|
|
||||||
'like': '喜歡',
|
|
||||||
'want': '想要',
|
|
||||||
'need': '需要',
|
|
||||||
'think': '思考',
|
|
||||||
'know': '知道',
|
|
||||||
'see': '看見',
|
|
||||||
'go': '去',
|
|
||||||
'come': '來',
|
|
||||||
'get': '得到',
|
|
||||||
'make': '製作',
|
|
||||||
'take': '拿取',
|
|
||||||
'give': '給予',
|
|
||||||
'find': '找到',
|
|
||||||
'work': '工作',
|
|
||||||
'feel': '感覺',
|
|
||||||
'become': '成為',
|
|
||||||
'leave': '離開',
|
|
||||||
'put': '放置',
|
|
||||||
'mean': '意思',
|
|
||||||
'keep': '保持',
|
|
||||||
'let': '讓',
|
|
||||||
'begin': '開始',
|
|
||||||
'seem': '似乎',
|
|
||||||
'help': '幫助',
|
|
||||||
'talk': '談話',
|
|
||||||
'turn': '轉向',
|
|
||||||
'start': '開始',
|
|
||||||
'show': '顯示',
|
|
||||||
'hear': '聽見',
|
|
||||||
'play': '玩耍',
|
|
||||||
'run': '跑步',
|
|
||||||
'move': '移動',
|
|
||||||
'live': '生活',
|
|
||||||
'believe': '相信',
|
|
||||||
'bring': '帶來',
|
|
||||||
'happen': '發生',
|
|
||||||
'write': '寫作',
|
|
||||||
'provide': '提供',
|
|
||||||
'sit': '坐下',
|
|
||||||
'stand': '站立',
|
|
||||||
'lose': '失去',
|
|
||||||
'pay': '付費',
|
|
||||||
'meet': '遇見',
|
|
||||||
'include': '包含',
|
|
||||||
'continue': '繼續',
|
|
||||||
'set': '設置',
|
|
||||||
'learn': '學習',
|
|
||||||
'change': '改變',
|
|
||||||
'lead': '領導',
|
|
||||||
'understand': '理解',
|
|
||||||
'watch': '觀看',
|
|
||||||
'follow': '跟隨',
|
|
||||||
'stop': '停止',
|
|
||||||
'create': '創造',
|
|
||||||
'speak': '說話',
|
|
||||||
'read': '閱讀',
|
|
||||||
'allow': '允許',
|
|
||||||
'add': '添加',
|
|
||||||
'spend': '花費',
|
|
||||||
'grow': '成長',
|
|
||||||
'open': '打開',
|
|
||||||
'walk': '走路',
|
|
||||||
'win': '獲勝',
|
|
||||||
'offer': '提供',
|
|
||||||
'remember': '記住',
|
|
||||||
'consider': '考慮',
|
|
||||||
'appear': '出現',
|
|
||||||
'buy': '購買',
|
|
||||||
'wait': '等待',
|
|
||||||
'serve': '服務',
|
|
||||||
'die': '死亡',
|
|
||||||
'send': '發送',
|
|
||||||
'expect': '期待',
|
|
||||||
'build': '建造',
|
|
||||||
'stay': '停留',
|
|
||||||
'fall': '跌倒',
|
|
||||||
'cut': '切割',
|
|
||||||
'reach': '到達',
|
|
||||||
'kill': '殺死',
|
|
||||||
'remain': '保持'
|
|
||||||
}
|
|
||||||
|
|
||||||
return commonTranslations[word.toLowerCase()] || `${word}的翻譯`
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRandomPartOfSpeech = () => {
|
|
||||||
const parts = ['noun', 'verb', 'adjective', 'adverb', 'preposition']
|
|
||||||
return parts[Math.floor(Math.random() * parts.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRandomDifficulty = () => {
|
|
||||||
const levels = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']
|
|
||||||
return levels[Math.floor(Math.random() * levels.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRandomSynonym = (word: string) => {
|
|
||||||
return `synonym_${word}_${Math.floor(Math.random() * 10)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAcceptCorrection = () => {
|
const handleAcceptCorrection = () => {
|
||||||
if (grammarCorrection?.correctedText) {
|
if (grammarCorrection?.correctedText) {
|
||||||
|
|
@ -225,8 +90,9 @@ function GenerateContent() {
|
||||||
const handleSaveWord = async (word: string, analysis: any) => {
|
const handleSaveWord = async (word: string, analysis: any) => {
|
||||||
try {
|
try {
|
||||||
const cardData = {
|
const cardData = {
|
||||||
english: word, // 修正API欄位名稱
|
word: word,
|
||||||
chinese: analysis.translation || analysis.Translation || '',
|
translation: analysis.translation || analysis.Translation || '',
|
||||||
|
definition: analysis.definition || analysis.Definition || '',
|
||||||
pronunciation: analysis.pronunciation || analysis.Pronunciation || `/${word}/`,
|
pronunciation: analysis.pronunciation || analysis.Pronunciation || `/${word}/`,
|
||||||
partOfSpeech: analysis.partOfSpeech || analysis.PartOfSpeech || 'unknown',
|
partOfSpeech: analysis.partOfSpeech || analysis.PartOfSpeech || 'unknown',
|
||||||
example: `Example sentence with ${word}.` // 提供預設例句
|
example: `Example sentence with ${word}.` // 提供預設例句
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,9 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
|
|
||||||
const [formData, setFormData] = useState<CreateFlashcardRequest>({
|
const [formData, setFormData] = useState<CreateFlashcardRequest>({
|
||||||
cardSetId: getDefaultCardSetId(),
|
cardSetId: getDefaultCardSetId(),
|
||||||
english: initialData?.english || '',
|
word: initialData?.word || '',
|
||||||
chinese: initialData?.chinese || '',
|
translation: initialData?.translation || '',
|
||||||
|
definition: initialData?.definition || '',
|
||||||
pronunciation: initialData?.pronunciation || '',
|
pronunciation: initialData?.pronunciation || '',
|
||||||
partOfSpeech: initialData?.partOfSpeech || '名詞',
|
partOfSpeech: initialData?.partOfSpeech || '名詞',
|
||||||
example: initialData?.example || '',
|
example: initialData?.example || '',
|
||||||
|
|
@ -158,16 +159,16 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.english}
|
value={formData.word}
|
||||||
onChange={(e) => handleChange('english', e.target.value)}
|
onChange={(e) => handleChange('word', e.target.value)}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
placeholder="例如:negotiate"
|
placeholder="例如:negotiate"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{formData.english && (
|
{formData.word && (
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<AudioPlayer
|
<AudioPlayer
|
||||||
text={formData.english}
|
text={formData.word}
|
||||||
className="w-auto"
|
className="w-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -182,8 +183,8 @@ export function FlashcardForm({ cardSets, initialData, isEdit = false, onSuccess
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.chinese}
|
value={formData.translation}
|
||||||
onChange={(e) => handleChange('chinese', e.target.value)}
|
onChange={(e) => handleChange('translation', e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||||
placeholder="例如:談判,協商"
|
placeholder="例如:談判,協商"
|
||||||
required
|
required
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,9 @@ export interface CreateCardSetRequest {
|
||||||
|
|
||||||
export interface CreateFlashcardRequest {
|
export interface CreateFlashcardRequest {
|
||||||
cardSetId?: string;
|
cardSetId?: string;
|
||||||
english: string;
|
word: string;
|
||||||
chinese: string;
|
translation: string;
|
||||||
|
definition: string;
|
||||||
pronunciation: string;
|
pronunciation: string;
|
||||||
partOfSpeech: string;
|
partOfSpeech: string;
|
||||||
example: string;
|
example: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue