273 lines
9.2 KiB
TypeScript
273 lines
9.2 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
|
||
interface GrammarCorrection {
|
||
hasErrors: boolean
|
||
originalText: string
|
||
correctedText: string | null
|
||
corrections: Array<{
|
||
position: { start: number; end: number }
|
||
errorType: string
|
||
original: string
|
||
corrected: string
|
||
reason: string
|
||
severity: 'high' | 'medium' | 'low'
|
||
}>
|
||
confidenceScore: number
|
||
}
|
||
|
||
interface GrammarCorrectionPanelProps {
|
||
correction: GrammarCorrection
|
||
onAcceptCorrection: () => void
|
||
onRejectCorrection: () => void
|
||
onManualEdit?: (text: string) => void
|
||
}
|
||
|
||
export function GrammarCorrectionPanel({
|
||
correction,
|
||
onAcceptCorrection,
|
||
onRejectCorrection,
|
||
onManualEdit
|
||
}: GrammarCorrectionPanelProps) {
|
||
const [isExpanded, setIsExpanded] = useState(true)
|
||
|
||
if (!correction.hasErrors) {
|
||
return (
|
||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
|
||
<div className="flex items-center gap-2">
|
||
<div className="text-green-600 text-lg">✅</div>
|
||
<div>
|
||
<div className="font-medium text-green-800">語法檢查:無錯誤</div>
|
||
<div className="text-sm text-green-700">
|
||
您的句子語法正確,可以直接進行學習分析!
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const renderHighlightedText = (text: string, corrections: typeof correction.corrections) => {
|
||
if (corrections.length === 0) return text
|
||
|
||
let result: React.ReactNode[] = []
|
||
let lastIndex = 0
|
||
|
||
corrections.forEach((corr, index) => {
|
||
// 添加錯誤前的正常文字
|
||
if (corr.position.start > lastIndex) {
|
||
result.push(
|
||
<span key={`normal-${index}`}>
|
||
{text.slice(lastIndex, corr.position.start)}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
// 添加錯誤文字(紅色標記)
|
||
result.push(
|
||
<span
|
||
key={`error-${index}`}
|
||
className="relative bg-red-100 border-b-2 border-red-400 px-1 rounded"
|
||
title={`錯誤:${corr.reason}`}
|
||
>
|
||
{corr.original}
|
||
<span className="absolute -top-1 -right-1 text-xs text-red-600">❌</span>
|
||
</span>
|
||
)
|
||
|
||
lastIndex = corr.position.end
|
||
})
|
||
|
||
// 添加最後剩餘的正常文字
|
||
if (lastIndex < text.length) {
|
||
result.push(
|
||
<span key="final">
|
||
{text.slice(lastIndex)}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
return <>{result}</>
|
||
}
|
||
|
||
const renderCorrectedText = (text: string, corrections: typeof correction.corrections) => {
|
||
if (corrections.length === 0 || !text) return text
|
||
|
||
let result: React.ReactNode[] = []
|
||
let lastIndex = 0
|
||
let offset = 0 // 修正後文字長度變化的偏移量
|
||
|
||
corrections.forEach((corr, index) => {
|
||
const adjustedStart = corr.position.start + offset
|
||
const originalLength = corr.original.length
|
||
const correctedLength = corr.corrected.length
|
||
|
||
// 添加修正前的正常文字
|
||
if (adjustedStart > lastIndex) {
|
||
result.push(
|
||
<span key={`normal-${index}`}>
|
||
{text.slice(lastIndex, adjustedStart)}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
// 添加修正後的文字(綠色標記)
|
||
result.push(
|
||
<span
|
||
key={`corrected-${index}`}
|
||
className="relative bg-green-100 border-b-2 border-green-400 px-1 rounded font-medium"
|
||
title={`修正:${corr.reason}`}
|
||
>
|
||
{corr.corrected}
|
||
<span className="absolute -top-1 -right-1 text-xs text-green-600">✅</span>
|
||
</span>
|
||
)
|
||
|
||
lastIndex = adjustedStart + correctedLength
|
||
offset += (correctedLength - originalLength)
|
||
})
|
||
|
||
// 添加最後剩餘的正常文字
|
||
if (lastIndex < text.length) {
|
||
result.push(
|
||
<span key="final">
|
||
{text.slice(lastIndex)}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
return <>{result}</>
|
||
}
|
||
|
||
const getSeverityColor = (severity: string) => {
|
||
switch (severity) {
|
||
case 'high':
|
||
return 'bg-red-100 text-red-700 border-red-300'
|
||
case 'medium':
|
||
return 'bg-yellow-100 text-yellow-700 border-yellow-300'
|
||
case 'low':
|
||
return 'bg-blue-100 text-blue-700 border-blue-300'
|
||
default:
|
||
return 'bg-gray-100 text-gray-700 border-gray-300'
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="bg-white border border-red-200 rounded-lg shadow-sm mb-6">
|
||
{/* 標題區 */}
|
||
<div className="bg-red-50 px-6 py-4 border-b border-red-200 rounded-t-lg">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="text-red-600 text-xl">❌</div>
|
||
<div>
|
||
<h3 className="font-semibold text-red-800">
|
||
語法檢查:發現 {correction.corrections.length} 個錯誤
|
||
</h3>
|
||
<div className="text-sm text-red-700">
|
||
建議修正後再進行學習,以確保學習內容的正確性
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => setIsExpanded(!isExpanded)}
|
||
className="text-red-600 hover:text-red-800"
|
||
>
|
||
{isExpanded ? '收起' : '展開'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{isExpanded && (
|
||
<div className="p-6 space-y-6">
|
||
{/* 原始句子 */}
|
||
<div>
|
||
<h4 className="font-medium text-gray-800 mb-2">📝 用戶輸入:</h4>
|
||
<div className="p-4 bg-gray-50 rounded-lg border border-gray-200">
|
||
<div className="text-lg leading-relaxed">
|
||
{renderHighlightedText(correction.originalText, correction.corrections)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 修正建議 */}
|
||
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h4 className="font-medium text-green-800 mb-3 flex items-center gap-2">
|
||
<span className="text-lg">🔧</span>
|
||
建議修正:
|
||
</h4>
|
||
|
||
<div className="p-4 bg-white rounded-lg border border-green-300 mb-4">
|
||
<div className="text-lg leading-relaxed">
|
||
{correction.correctedText && renderCorrectedText(correction.correctedText, correction.corrections)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 修正說明列表 */}
|
||
<div className="space-y-3">
|
||
<h5 className="font-medium text-green-800">📋 修正說明:</h5>
|
||
{correction.corrections.map((corr, index) => (
|
||
<div
|
||
key={index}
|
||
className={`p-3 rounded-lg border ${getSeverityColor(corr.severity)}`}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<div className="flex-shrink-0 w-6 h-6 rounded-full bg-white flex items-center justify-center text-sm font-bold">
|
||
{index + 1}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="font-medium mb-1">
|
||
"{corr.original}" → "{corr.corrected}"
|
||
</div>
|
||
<div className="text-sm">
|
||
{corr.reason}
|
||
</div>
|
||
<div className="text-xs mt-1 opacity-75">
|
||
錯誤類型:{corr.errorType} | 嚴重程度:{corr.severity}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 信心度 */}
|
||
<div className="mt-4 text-sm text-green-700">
|
||
🎯 修正信心度:{(correction.confidenceScore * 100).toFixed(1)}%
|
||
</div>
|
||
</div>
|
||
|
||
{/* 操作按鈕 */}
|
||
<div className="flex gap-4">
|
||
<button
|
||
onClick={onAcceptCorrection}
|
||
className="flex-1 bg-green-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<span className="text-lg">✅</span>
|
||
使用修正版本
|
||
</button>
|
||
<button
|
||
onClick={onRejectCorrection}
|
||
className="flex-1 bg-gray-200 text-gray-700 py-3 px-4 rounded-lg font-medium hover:bg-gray-300 transition-colors flex items-center justify-center gap-2"
|
||
>
|
||
<span className="text-lg">❌</span>
|
||
保持原始版本
|
||
</button>
|
||
</div>
|
||
|
||
{/* 學習提醒 */}
|
||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
|
||
<div className="flex items-start gap-2">
|
||
<div className="text-blue-600 text-lg">💡</div>
|
||
<div className="text-sm text-blue-800">
|
||
<strong>學習建議:</strong>
|
||
建議使用修正版本進行學習,這樣可以確保您學到正確的英語表達方式。
|
||
所有後續的詞彙分析都將基於修正後的句子進行。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
} |