Update from Vibe Studio

This commit is contained in:
Vibe Studio
2026-02-04 07:56:24 +00:00
parent 976704f9ec
commit 4332632f15

View File

@@ -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