Update from Vibe Studio
This commit is contained in:
@@ -1,79 +1,59 @@
|
|||||||
import { useState } from 'react'
|
import { useState, useCallback } from 'react'
|
||||||
import { Button, Card, Form, Input, message, Space, Switch } from 'antd'
|
import { Button, Card, Form, Input, message, Space, Typography } from 'antd'
|
||||||
import { checkAndCorrectSpelling } from '@/api/spellCheck'
|
import { CopyOutlined, ClearOutlined, CheckCircleOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
interface CorrectionResult {
|
const { Title, Text, Paragraph } = Typography
|
||||||
original: string
|
|
||||||
corrected: string
|
interface SpellCheckPageProps {
|
||||||
position: number
|
// Props definition if needed
|
||||||
suggestions?: string[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpellCheck = () => {
|
const SpellCheckPage: React.FC<SpellCheckPageProps> = () => {
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
const [isGenerating, setIsGenerating] = useState(false)
|
const [isChecking, setIsChecking] = useState(false)
|
||||||
const [correctionResults, setCorrectionResults] = useState<CorrectionResult[]>([])
|
const [result, setResult] = useState<string>('')
|
||||||
const [correctedText, setCorrectedText] = useState('')
|
const [originalText, setOriginalText] = useState<string>('')
|
||||||
const maxChars = 5000
|
const maxChars = 5000
|
||||||
|
|
||||||
const validateCustomVocab = (_: any, value: string) => {
|
// 调用Dify API进行错别字校验
|
||||||
if (!value) {
|
const handleCheck = useCallback(async () => {
|
||||||
return Promise.resolve()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(value)
|
const values = await form.validateFields(['text_to_check'])
|
||||||
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 () => {
|
if (!values.text_to_check?.trim()) {
|
||||||
try {
|
message.warning('请输入需要校验的文本内容')
|
||||||
const formValues = await form.validateFields()
|
|
||||||
|
|
||||||
// 验证用户输入文本
|
|
||||||
if (!formValues.user_input_text?.trim()) {
|
|
||||||
message.warning('请输入需要检测错别字的文本')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证文本长度
|
if (values.text_to_check.length > maxChars) {
|
||||||
if (formValues.user_input_text.length > maxChars) {
|
|
||||||
message.warning(`输入文本不能超过${maxChars}个字符`)
|
message.warning(`输入文本不能超过${maxChars}个字符`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsGenerating(true)
|
setIsChecking(true)
|
||||||
setCorrectionResults([])
|
setResult('')
|
||||||
setCorrectedText('')
|
setOriginalText(values.text_to_check)
|
||||||
|
|
||||||
let customVocabArray: string[] = []
|
const API_URL = 'https://copilot.sino-bridge.com/v1/chat-messages'
|
||||||
if (formValues.custom_vocab) {
|
const API_KEY = 'app-Ykwky9aKr95GBwm9FWFUg6ot'
|
||||||
try {
|
|
||||||
customVocabArray = JSON.parse(formValues.custom_vocab)
|
|
||||||
} catch (error) {
|
|
||||||
message.error('自定义词汇库格式错误')
|
|
||||||
setIsGenerating(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await checkAndCorrectSpelling(
|
const response = await fetch(API_URL, {
|
||||||
formValues.user_input_text,
|
method: 'POST',
|
||||||
formValues.auto_correct || false,
|
headers: {
|
||||||
customVocabArray
|
'Content-Type': 'application/json',
|
||||||
)
|
'Authorization': `Bearer ${API_KEY}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
inputs: {
|
||||||
|
text_to_check: values.text_to_check
|
||||||
|
},
|
||||||
|
query: '1',
|
||||||
|
response_mode: 'streaming'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorText = await response.text()
|
throw new Error(`API请求失败: ${response.status}`)
|
||||||
throw new Error(`检测请求失败: ${response.status} ${errorText}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
@@ -84,7 +64,6 @@ const SpellCheck = () => {
|
|||||||
const decoder = new TextDecoder('utf-8')
|
const decoder = new TextDecoder('utf-8')
|
||||||
let buffer = ''
|
let buffer = ''
|
||||||
let fullContent = ''
|
let fullContent = ''
|
||||||
let results: CorrectionResult[] = []
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -99,7 +78,7 @@ const SpellCheck = () => {
|
|||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
|
if (!trimmedLine || trimmedLine === 'data: [DONE]') {
|
||||||
if (trimmedLine === 'data: [DONE]') {
|
if (trimmedLine === 'data: [DONE]') {
|
||||||
message.success('检测完成')
|
message.success('校验完成')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -112,27 +91,12 @@ const SpellCheck = () => {
|
|||||||
|
|
||||||
if (parsed.event === 'message' && parsed.answer) {
|
if (parsed.event === 'message' && parsed.answer) {
|
||||||
fullContent += parsed.answer
|
fullContent += parsed.answer
|
||||||
// 尝试解析JSON结果
|
setResult(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) {
|
|
||||||
// 解析失败,继续等待
|
|
||||||
}
|
|
||||||
} else if (parsed.event === 'error') {
|
} else if (parsed.event === 'error') {
|
||||||
throw new Error(parsed.message || 'Dify API 返回错误')
|
throw new Error(parsed.message || 'Dify API 返回错误')
|
||||||
}
|
}
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.warn('跳过无法解析的行:', trimmedLine)
|
// 跳过无法解析的行
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,202 +105,156 @@ const SpellCheck = () => {
|
|||||||
reader.releaseLock()
|
reader.releaseLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.length === 0 && fullContent) {
|
if (!fullContent) {
|
||||||
try {
|
message.warning('未能获取到校验结果')
|
||||||
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) {
|
} catch (error) {
|
||||||
console.error('检测错误:', error)
|
console.error('校验错误:', error)
|
||||||
message.error(error instanceof Error ? error.message : '检测失败,请稍后重试')
|
message.error(error instanceof Error ? error.message : '校验失败,请稍后重试')
|
||||||
} finally {
|
} finally {
|
||||||
setIsGenerating(false)
|
setIsChecking(false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, [form])
|
||||||
|
|
||||||
const handleReset = () => {
|
// 清空内容
|
||||||
|
const handleClear = useCallback(() => {
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
setCorrectionResults([])
|
setResult('')
|
||||||
setCorrectedText('')
|
setOriginalText('')
|
||||||
message.info('已重置表单')
|
message.info('已清空内容')
|
||||||
|
}, [form])
|
||||||
|
|
||||||
|
// 复制结果
|
||||||
|
const handleCopy = useCallback(async () => {
|
||||||
|
if (!result) {
|
||||||
|
message.warning('没有可复制的内容')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderCorrectionResults = () => {
|
try {
|
||||||
if (isGenerating) {
|
await navigator.clipboard.writeText(result)
|
||||||
|
message.success('已复制到剪贴板')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('复制失败,请手动选择复制')
|
||||||
|
}
|
||||||
|
}, [result])
|
||||||
|
|
||||||
|
// 渲染校验结果
|
||||||
|
const renderResult = () => {
|
||||||
|
if (isChecking) {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
|
<div style={{ textAlign: 'center', padding: '40px 20px' }}>
|
||||||
正在检测中,请稍候...
|
<Text type='secondary'>正在校验中,请稍候...</Text>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (correctionResults.length === 0) {
|
if (!result && !originalText) {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', color: '#999' }}>
|
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#999' }}>
|
||||||
错别字检测结果将在这里显示
|
<CheckCircleOutlined style={{ fontSize: 48, color: '#d9d9d9', marginBottom: 16 }} />
|
||||||
|
<br />
|
||||||
|
<Text type='secondary'>校验结果将在这里显示</Text>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
<div style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', lineHeight: 1.8 }}>
|
||||||
{correctionResults.map((item, index) => (
|
{result || ''}
|
||||||
<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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px' }}>
|
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '40px 20px' }}>
|
||||||
{/* 页面标题区 */}
|
{/* 页面标题区 */}
|
||||||
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
|
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
|
||||||
<h1 style={{ color: '#1890ff', fontSize: '32px', fontWeight: 'bold', margin: 0 }}>
|
<Title level={2} style={{ color: '#1890ff', marginBottom: 8 }}>
|
||||||
错别字检测与纠正系统
|
错别字校验工具
|
||||||
</h1>
|
</Title>
|
||||||
|
<Text type='secondary'>
|
||||||
|
上传文档,AI将自动检测并标记错别字
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
<Space direction='vertical' style={{ width: '100%' }} size='large'>
|
||||||
|
|
||||||
{/* 参数输入区 */}
|
{/* 参数输入区 */}
|
||||||
<Card title="参数输入区">
|
<Card style={{ borderRadius: 8 }}>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout='vertical'>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="user_input_text"
|
name='text_to_check'
|
||||||
label="用户输入文本"
|
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入需要检测错别字的文本' },
|
{ required: true, message: '请输入需要校验的文本内容' },
|
||||||
{ max: maxChars, message: `输入文本不能超过${maxChars}个字符` }
|
{ max: maxChars, message: `输入文本不能超过${maxChars}个字符` }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={8}
|
rows={6}
|
||||||
placeholder="请输入需要检测错别字的中文文本..."
|
placeholder='请输入或粘贴需要校验的文本内容...'
|
||||||
maxLength={maxChars}
|
maxLength={maxChars}
|
||||||
showCount
|
showCount
|
||||||
style={{ fontSize: '14px' }}
|
style={{ fontSize: 14, borderRadius: 6 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</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>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 操作按钮区 */}
|
{/* 操作按钮区 */}
|
||||||
<div className="button-group" style={{ display: 'flex', justifyContent: 'center', gap: '16px' }}>
|
<div style={{ display: 'flex', justifyContent: 'center', gap: 16 }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type='primary'
|
||||||
size="large"
|
size='large'
|
||||||
onClick={handleGenerate}
|
onClick={handleCheck}
|
||||||
loading={isGenerating}
|
loading={isChecking}
|
||||||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
style={{ minWidth: 120, height: 44, borderRadius: 6 }}
|
||||||
>
|
>
|
||||||
检测错别字
|
开始校验
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="large"
|
size='large'
|
||||||
onClick={handleReset}
|
onClick={handleClear}
|
||||||
disabled={isGenerating}
|
disabled={isChecking}
|
||||||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
icon={<ClearOutlined />}
|
||||||
|
style={{ minWidth: 120, height: 44, borderRadius: 6 }}
|
||||||
>
|
>
|
||||||
重置
|
清空内容
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size='large'
|
||||||
|
onClick={handleCopy}
|
||||||
|
disabled={!result || isChecking}
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
style={{ minWidth: 120, height: 44, borderRadius: 6 }}
|
||||||
|
>
|
||||||
|
复制结果
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 内容展示区 */}
|
{/* 内容展示区 */}
|
||||||
<Card
|
<Card
|
||||||
title="检测结果"
|
title='校验结果'
|
||||||
style={{
|
style={{ borderRadius: 8 }}
|
||||||
display: correctionResults.length > 0 || isGenerating ? 'block' : 'none'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
minHeight: '150px',
|
minHeight: 150,
|
||||||
maxHeight: '400px',
|
maxHeight: '50vh',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
padding: '15px',
|
padding: 16,
|
||||||
backgroundColor: '#fafafa',
|
backgroundColor: '#fafafa',
|
||||||
border: '1px solid #d9d9d9',
|
border: '1px solid #d9d9d9',
|
||||||
borderRadius: '4px'
|
borderRadius: 6
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderCorrectionResults()}
|
{renderResult()}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SpellCheck
|
export default SpellCheckPage
|
||||||
|
|||||||
Reference in New Issue
Block a user