Files
copilot-toolbox-template334…/src/pages/spell-check/index.tsx
2026-01-16 06:05:34 +00:00

343 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, message, Space, Switch } from 'antd'
import { checkAndCorrectSpelling } from '@/api/spellCheck'
interface CorrectionResult {
original: string
corrected: string
position: number
suggestions?: string[]
}
const SpellCheck = () => {
const [form] = Form.useForm()
const [isGenerating, setIsGenerating] = useState(false)
const [correctionResults, setCorrectionResults] = useState<CorrectionResult[]>([])
const [correctedText, setCorrectedText] = useState('')
const maxChars = 5000
const validateCustomVocab = (_: any, value: string) => {
if (!value) {
return Promise.resolve()
}
try {
const parsed = JSON.parse(value)
if (!Array.isArray(parsed)) {
return Promise.reject(new Error('必须是JSON数组格式'))
}
if (!parsed.every(item => typeof item === 'string')) {
return Promise.reject(new Error('数组元素必须为字符串'))
}
return Promise.resolve()
} catch (error) {
return Promise.reject(new Error('JSON格式无效'))
}
}
const handleGenerate = async () => {
try {
const formValues = await form.validateFields()
// 验证用户输入文本
if (!formValues.user_input_text?.trim()) {
message.warning('请输入需要检测错别字的文本')
return
}
// 验证文本长度
if (formValues.user_input_text.length > maxChars) {
message.warning(`输入文本不能超过${maxChars}个字符`)
return
}
setIsGenerating(true)
setCorrectionResults([])
setCorrectedText('')
let customVocabArray: string[] = []
if (formValues.custom_vocab) {
try {
customVocabArray = JSON.parse(formValues.custom_vocab)
} catch (error) {
message.error('自定义词汇库格式错误')
setIsGenerating(false)
return
}
}
const response = await checkAndCorrectSpelling(
formValues.user_input_text,
formValues.auto_correct || false,
customVocabArray
)
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: CorrectionResult[] = []
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)
setCorrectionResults(results)
if (formValues.auto_correct) {
let corrected = formValues.user_input_text
results.forEach(item => {
if (item.corrected && item.original) {
corrected = corrected.replace(item.original, item.corrected)
}
})
setCorrectedText(corrected)
}
} 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)
setCorrectionResults(results)
if (formValues.auto_correct) {
let corrected = formValues.user_input_text
results.forEach(item => {
if (item.corrected && item.original) {
corrected = corrected.replace(item.original, item.corrected)
}
})
setCorrectedText(corrected)
}
} catch (e) {
message.warning('检测完成,但结果格式异常')
}
}
} catch (error) {
console.error('检测错误:', error)
message.error(error instanceof Error ? error.message : '检测失败,请稍后重试')
} finally {
setIsGenerating(false)
}
}
const handleReset = () => {
form.resetFields()
setCorrectionResults([])
setCorrectedText('')
message.info('已重置表单')
}
const renderCorrectionResults = () => {
if (isGenerating) {
return (
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
...
</div>
)
}
if (correctionResults.length === 0) {
return (
<div style={{ textAlign: 'center', color: '#999' }}>
</div>
)
}
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{correctionResults.map((item, index) => (
<div
key={index}
style={{
padding: '12px',
backgroundColor: '#f5f5f5',
borderRadius: '6px',
border: '1px solid #d9d9d9'
}}
>
<div style={{ marginBottom: '8px', fontWeight: 'bold' }}>
{item.position}
</div>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<span style={{ color: '#ff4d4f' }}>{item.original}</span>
<span></span>
<span style={{ color: '#52c41a' }}>{item.corrected}</span>
</div>
{item.suggestions && item.suggestions.length > 0 && (
<div style={{ marginTop: '8px', fontSize: '12px', color: '#666' }}>
{item.suggestions.join('、')}
</div>
)}
</div>
))}
</div>
)
}
return (
<div style={{ maxWidth: '900px', 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">
<Form.Item
name="user_input_text"
label="用户输入文本"
rules={[
{ required: true, message: '请输入需要检测错别字的文本' },
{ max: maxChars, message: `输入文本不能超过${maxChars}个字符` }
]}
>
<Input.TextArea
rows={8}
placeholder="请输入需要检测错别字的中文文本..."
maxLength={maxChars}
showCount
style={{ fontSize: '14px' }}
/>
</Form.Item>
<Form.Item name="auto_correct" label="是否自动替换" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name="custom_vocab"
label="自定义词汇库(可选)"
rules={[{ validator: validateCustomVocab }]}
>
<Input.TextArea
rows={4}
placeholder='请输入JSON格式词汇库["正确词1", "正确词2"]'
style={{ fontSize: '14px' }}
/>
<div className="form-helper-text" style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
JSON数组
</div>
</Form.Item>
</Form>
</Card>
{/* 操作按钮区 */}
<div className="button-group" style={{ display: 'flex', justifyContent: 'center', gap: '16px' }}>
<Button
type="primary"
size="large"
onClick={handleGenerate}
loading={isGenerating}
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleReset}
disabled={isGenerating}
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
>
</Button>
</div>
{/* 内容展示区 */}
<Card
title="检测结果"
style={{
display: correctionResults.length > 0 || isGenerating ? 'block' : 'none'
}}
>
<div
style={{
minHeight: '150px',
maxHeight: '400px',
overflowY: 'auto',
padding: '15px',
backgroundColor: '#fafafa',
border: '1px solid #d9d9d9',
borderRadius: '4px'
}}
>
{renderCorrectionResults()}
</div>
</Card>
{correctedText && (
<Card title="纠正后的文本">
<div
style={{
minHeight: '120px',
padding: '15px',
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '4px',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
fontSize: '14px',
lineHeight: '1.6'
}}
>
{correctedText}
</div>
</Card>
)}
</Space>
</div>
)
}
export default SpellCheck