Files
copilot-toolbox-template-21/src/pages/spell-check-system/index.tsx
2026-01-16 06:29:38 +00:00

357 lines
11 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.
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