357 lines
11 KiB
TypeScript
357 lines
11 KiB
TypeScript
import { useState } from 'react'
|
||
import { Button, Card, Form, Input, Table, Switch, Space, message } from 'antd'
|
||
import { detectSpellingErrors, SpellCheckResult } from '@/api/spell-check-system'
|
||
|
||
interface FeedbackItem extends SpellCheckResult {
|
||
id: string
|
||
status: 'pending' | 'confirmed' | 'rejected'
|
||
}
|
||
|
||
const SpellCheckSystem = () => {
|
||
const [form] = Form.useForm()
|
||
const [isDetecting, setIsDetecting] = useState(false)
|
||
const [detectionResults, setDetectionResults] = useState<FeedbackItem[]>([])
|
||
const [feedbackData, setFeedbackData] = useState<{
|
||
confirmed: string[]
|
||
rejected: string[]
|
||
}>({
|
||
confirmed: [],
|
||
rejected: []
|
||
})
|
||
|
||
const handleDetect = async () => {
|
||
try {
|
||
const formValues = await form.validateFields()
|
||
|
||
// 验证用户输入文本
|
||
if (!formValues.text_input?.trim()) {
|
||
message.warning('请输入需要检测的文本内容')
|
||
return
|
||
}
|
||
|
||
setIsDetecting(true)
|
||
setDetectionResults([])
|
||
|
||
const response = await detectSpellingErrors({
|
||
text_input: formValues.text_input,
|
||
enable_error_category: formValues.enable_error_category || false,
|
||
enable_correction_suggestion: formValues.enable_correction_suggestion !== false,
|
||
enable_batch_processing: formValues.enable_batch_processing || false
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text()
|
||
throw new Error(`检测请求失败: ${response.status} ${errorText}`)
|
||
}
|
||
|
||
if (!response.body) {
|
||
throw new Error('响应体为空')
|
||
}
|
||
|
||
const reader = response.body.getReader()
|
||
const decoder = new TextDecoder('utf-8')
|
||
let buffer = ''
|
||
let fullContent = ''
|
||
let results: SpellCheckResult[] = []
|
||
|
||
try {
|
||
while (true) {
|
||
const { done, value } = await reader.read()
|
||
if (done) break
|
||
|
||
buffer += decoder.decode(value, { stream: true })
|
||
const lines = buffer.split('\n')
|
||
buffer = lines.pop() || ''
|
||
|
||
for (const line of lines) {
|
||
const trimmedLine = line.trim()
|
||
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
|
||
if (trimmedLine === 'data: [DONE]') {
|
||
message.success('检测完成')
|
||
break
|
||
}
|
||
continue
|
||
}
|
||
|
||
if (trimmedLine.startsWith('data: ')) {
|
||
try {
|
||
const data = trimmedLine.slice(6)
|
||
const parsed = JSON.parse(data)
|
||
|
||
if (parsed.event === 'message' && parsed.answer) {
|
||
fullContent += parsed.answer
|
||
// 尝试解析JSON结果
|
||
try {
|
||
results = JSON.parse(fullContent)
|
||
const feedbackItems: FeedbackItem[] = results.map((item, index) => ({
|
||
...item,
|
||
id: `item-${index}`,
|
||
status: 'pending' as const
|
||
}))
|
||
setDetectionResults(feedbackItems)
|
||
} catch (e) {
|
||
// 解析失败,继续等待
|
||
}
|
||
} else if (parsed.event === 'error') {
|
||
throw new Error(parsed.message || 'Dify API 返回错误')
|
||
}
|
||
} catch (parseError) {
|
||
console.warn('跳过无法解析的行:', trimmedLine)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} finally {
|
||
reader.releaseLock()
|
||
}
|
||
|
||
if (results.length === 0 && fullContent) {
|
||
try {
|
||
results = JSON.parse(fullContent)
|
||
const feedbackItems: FeedbackItem[] = results.map((item, index) => ({
|
||
...item,
|
||
id: `item-${index}`,
|
||
status: 'pending' as const
|
||
}))
|
||
setDetectionResults(feedbackItems)
|
||
} catch (e) {
|
||
message.warning('检测完成,但结果格式异常')
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('检测错误:', error)
|
||
message.error(error instanceof Error ? error.message : '检测失败,请稍后重试')
|
||
} finally {
|
||
setIsDetecting(false)
|
||
}
|
||
}
|
||
|
||
const handleFeedback = (id: string, status: 'confirmed' | 'rejected') => {
|
||
setDetectionResults(prev =>
|
||
prev.map(item =>
|
||
item.id === id ? { ...item, status } : item
|
||
)
|
||
)
|
||
}
|
||
|
||
const handleSubmitFeedback = () => {
|
||
const confirmed: string[] = []
|
||
const rejected: string[] = []
|
||
|
||
detectionResults.forEach(item => {
|
||
if (item.status === 'confirmed') {
|
||
confirmed.push(item.suggestion)
|
||
} else if (item.status === 'rejected') {
|
||
rejected.push(item.error_word)
|
||
}
|
||
})
|
||
|
||
const feedback = { confirmed, rejected }
|
||
|
||
// 存储到本地存储
|
||
localStorage.setItem('spell_check_feedback', JSON.stringify(feedback))
|
||
setFeedbackData(feedback)
|
||
|
||
message.success('反馈数据已提交并保存到本地存储')
|
||
}
|
||
|
||
const renderFeedbackButtons = (record: FeedbackItem) => {
|
||
if (record.status !== 'pending') {
|
||
return (
|
||
<span style={{ color: record.status === 'confirmed' ? '#52c41a' : '#ff4d4f', fontWeight: 'bold' }}>
|
||
{record.status === 'confirmed' ? '已确认' : '已否决'}
|
||
</span>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Space size="small">
|
||
<Button
|
||
size="small"
|
||
type="primary"
|
||
onClick={() => handleFeedback(record.id, 'confirmed')}
|
||
style={{ fontSize: '12px', height: '24px' }}
|
||
>
|
||
确认
|
||
</Button>
|
||
<Button
|
||
size="small"
|
||
danger
|
||
onClick={() => handleFeedback(record.id, 'rejected')}
|
||
style={{ fontSize: '12px', height: '24px' }}
|
||
>
|
||
否决
|
||
</Button>
|
||
</Space>
|
||
)
|
||
}
|
||
|
||
const columns = [
|
||
{
|
||
title: '错误词',
|
||
dataIndex: 'error_word',
|
||
key: 'error_word',
|
||
width: 120,
|
||
render: (text: string) => (
|
||
<span style={{ color: '#ff4d4f', fontWeight: 'bold' }}>{text}</span>
|
||
)
|
||
},
|
||
{
|
||
title: '上下文',
|
||
dataIndex: 'context',
|
||
key: 'context',
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: '建议词',
|
||
dataIndex: 'suggestion',
|
||
key: 'suggestion',
|
||
width: 120,
|
||
render: (text: string) => (
|
||
<span style={{ color: '#52c41a', fontWeight: 'bold' }}>{text}</span>
|
||
)
|
||
},
|
||
{
|
||
title: '错误类型',
|
||
dataIndex: 'error_type',
|
||
key: 'error_type',
|
||
width: 120,
|
||
render: (text: string) => text || '-'
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
width: 150,
|
||
render: (_: any, record: FeedbackItem) => renderFeedbackButtons(record)
|
||
}
|
||
]
|
||
|
||
return (
|
||
<div style={{ maxWidth: '1000px', margin: '0 auto', padding: '20px' }}>
|
||
{/* 页面标题区 */}
|
||
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
|
||
<h1 style={{ color: '#1890ff', fontSize: '32px', fontWeight: 'bold', margin: 0 }}>
|
||
错别字检测与修正系统
|
||
</h1>
|
||
</div>
|
||
|
||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||
{/* 参数输入区 */}
|
||
<Card title="参数输入区">
|
||
<Form form={form} layout="vertical" initialValues={{
|
||
enable_error_category: false,
|
||
enable_correction_suggestion: true,
|
||
enable_batch_processing: false
|
||
}}>
|
||
<Form.Item
|
||
name="text_input"
|
||
label="待检测文本"
|
||
rules={[
|
||
{ required: true, message: '请输入需要检测的文本内容' }
|
||
]}
|
||
>
|
||
<Input.TextArea
|
||
rows={6}
|
||
placeholder="请输入需要检测错别字的文本内容..."
|
||
style={{ fontSize: '14px' }}
|
||
/>
|
||
</Form.Item>
|
||
|
||
<div style={{ display: 'flex', gap: '24px', flexWrap: 'wrap', marginTop: '16px' }}>
|
||
<Form.Item
|
||
name="enable_error_category"
|
||
label="是否启用分类"
|
||
valuePropName="checked"
|
||
>
|
||
<Switch />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="enable_correction_suggestion"
|
||
label="是否启用建议"
|
||
valuePropName="checked"
|
||
>
|
||
<Switch />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="enable_batch_processing"
|
||
label="是否启用批量处理"
|
||
valuePropName="checked"
|
||
>
|
||
<Switch />
|
||
</Form.Item>
|
||
</div>
|
||
</Form>
|
||
</Card>
|
||
|
||
{/* 操作按钮区 */}
|
||
<div style={{ display: 'flex', justifyContent: 'center', gap: '16px' }}>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
onClick={handleDetect}
|
||
loading={isDetecting}
|
||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||
>
|
||
检测
|
||
</Button>
|
||
<Button
|
||
size="large"
|
||
onClick={() => {
|
||
form.resetFields()
|
||
setDetectionResults([])
|
||
setFeedbackData({ confirmed: [], rejected: [] })
|
||
message.info('已重置表单')
|
||
}}
|
||
disabled={isDetecting}
|
||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||
>
|
||
重置
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 内容展示区 */}
|
||
<Card
|
||
title="错别字检测结果"
|
||
style={{
|
||
display: detectionResults.length > 0 || isDetecting ? 'block' : 'none'
|
||
}}
|
||
>
|
||
{isDetecting ? (
|
||
<div style={{ textAlign: 'center', color: '#999', padding: '40px' }}>
|
||
正在检测中,请稍候...
|
||
</div>
|
||
) : detectionResults.length > 0 ? (
|
||
<>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={detectionResults}
|
||
rowKey="id"
|
||
pagination={{ pageSize: 10 }}
|
||
scroll={{ y: 400 }}
|
||
/>
|
||
<div style={{ marginTop: '16px', textAlign: 'center' }}>
|
||
<Button
|
||
type="primary"
|
||
onClick={handleSubmitFeedback}
|
||
disabled={detectionResults.every(item => item.status === 'pending')}
|
||
style={{ minWidth: '140px', height: '36px', fontSize: '14px' }}
|
||
>
|
||
提交反馈
|
||
</Button>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div style={{ textAlign: 'center', color: '#999' }}>
|
||
错别字检测结果将在这里显示
|
||
</div>
|
||
)}
|
||
</Card>
|
||
</Space>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default SpellCheckSystem
|