Update from Vibe Studio

This commit is contained in:
Vibe Studio
2026-01-23 05:36:11 +00:00
parent 976704f9ec
commit b582b05bc1
3 changed files with 396 additions and 1 deletions

48
src/api/spell-correct.ts Normal file
View File

@@ -0,0 +1,48 @@
export interface DifyRequest {
inputs: {
text_input: string
uploaded_files?: any[]
}
query: string
response_mode: string
}
export interface SpellCorrectResult {
result_text: string
}
export function uploadFile(file: File) {
const formData = new FormData()
formData.append('file', file)
return fetch('https://copilot.sino-bridge.com/v1/files/upload', {
method: 'POST',
headers: {
'Authorization': 'Bearer app-ygiEUNOOihWdBvSYKffUhdZg'
},
body: formData
}).then(res => res.json())
}
export function detectAndCorrectSpelling(params: {
text_input: string
uploaded_files?: any[]
}) {
const requestBody: DifyRequest = {
inputs: {
text_input: params.text_input,
uploaded_files: params.uploaded_files
},
query: '1',
response_mode: 'streaming'
}
return fetch('https://copilot.sino-bridge.com/v1/chat-messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer app-ygiEUNOOihWdBvSYKffUhdZg'
},
body: JSON.stringify(requestBody)
})
}

View File

@@ -0,0 +1,347 @@
import { useState } from 'react'
import {
Button,
Card,
Form,
Input,
Upload,
message,
Space,
Typography,
Divider
} from 'antd'
import { UploadOutlined, CopyOutlined, ReloadOutlined, CheckOutlined } from '@ant-design/icons'
import { detectAndCorrectSpelling, uploadFile, SpellCorrectResult } from '@/api/spell-correct'
const { Title, Text } = Typography
const { TextArea } = Input
const SpellCorrect = () => {
const [form] = Form.useForm()
const [isGenerating, setIsGenerating] = useState(false)
const [originalText, setOriginalText] = useState('')
const [correctedText, setCorrectedText] = useState('')
const [uploadedFileData, setUploadedFileData] = useState<any>(null)
const handleFileUpload = async (file: File) => {
try {
const result = await uploadFile(file)
setUploadedFileData(result)
message.success('文件上传成功')
return false // 阻止默认上传行为
} catch (error) {
console.error('文件上传失败:', error)
message.error('文件上传失败,请重试')
return false
}
}
const handleGenerate = async () => {
try {
const formValues = await form.validateFields()
// 验证用户输入文本
if (!formValues.user_input?.trim()) {
message.warning('请输入需要检测错别字的文本')
return
}
setIsGenerating(true)
setOriginalText(formValues.user_input)
setCorrectedText('')
// 准备文件数据
let filesList: any[] = []
if (uploadedFileData) {
filesList = [
{
...uploadedFileData,
preview_url: '1',
transfer_method: 'local_file',
user: 'admin'
}
]
}
const response = await detectAndCorrectSpelling({
text_input: formValues.user_input,
uploaded_files: filesList.length > 0 ? filesList : undefined
})
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 = ''
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
setCorrectedText(fullContent)
} else if (parsed.event === 'error') {
throw new Error(parsed.message || 'Dify API 返回错误')
}
} catch (parseError) {
console.warn('跳过无法解析的行:', trimmedLine)
}
}
}
}
} finally {
reader.releaseLock()
}
if (!fullContent) {
message.warning('未获取到检测结果')
}
} catch (error) {
console.error('检测错误:', error)
message.error(error instanceof Error ? error.message : '检测失败,请稍后重试')
} finally {
setIsGenerating(false)
}
}
const handleCopyResult = async () => {
if (!correctedText) {
message.warning('没有可复制的内容')
return
}
try {
await navigator.clipboard.writeText(correctedText)
message.success('已复制到剪贴板')
} catch (error) {
console.error('复制失败:', error)
message.error('复制失败,请手动复制')
}
}
const handleReset = () => {
form.resetFields()
setOriginalText('')
setCorrectedText('')
setUploadedFileData(null)
message.info('已重置表单')
}
return (
<div
style={{
maxWidth: '1000px',
margin: '0 auto',
padding: '20px',
minHeight: '100vh',
backgroundColor: '#ffffff'
}}
>
{/* 页面标题区 */}
<div style={{ textAlign: 'center', marginBottom: '40px' }}>
<Title level={2} style={{ color: '#1890ff', marginBottom: 0 }}>
</Title>
</div>
<Space direction="vertical" style={{ width: '100%' }} size="large">
{/* 参数输入区 */}
<Card>
<Form form={form} layout="vertical">
<Form.Item
name="user_input"
label={
<Text strong style={{ fontSize: '16px' }}>
</Text>
}
rules={[
{ required: true, message: '请输入需要检测错别字的文本' },
{ max: 10000, message: '输入文本不能超过10000个字符' }
]}
>
<TextArea
rows={8}
placeholder="请输入需要检测错别字的中文文本..."
style={{ fontSize: '14px', fontFamily: '微软雅黑, 苹方, sans-serif' }}
/>
</Form.Item>
<Form.Item
name="upload_file"
label={
<Text strong style={{ fontSize: '16px' }}>
</Text>
}
>
<Upload
beforeUpload={handleFileUpload}
showUploadList={true}
accept=".txt,.doc,.docx,.pdf"
maxCount={1}
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
<div style={{ marginTop: '8px', fontSize: '12px', color: '#666' }}>
TXT, DOC, DOCX, PDF
</div>
</Form.Item>
</Form>
</Card>
{/* 操作按钮区 */}
<div style={{ display: 'flex', justifyContent: 'center', gap: '16px', flexWrap: 'wrap' }}>
<Button
type="primary"
size="large"
onClick={handleGenerate}
loading={isGenerating}
icon={<CheckOutlined />}
style={{ minWidth: '140px', height: '44px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleCopyResult}
disabled={!correctedText}
icon={<CopyOutlined />}
style={{ minWidth: '140px', height: '44px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleGenerate}
loading={isGenerating}
disabled={!originalText}
icon={<ReloadOutlined />}
style={{ minWidth: '140px', height: '44px', fontSize: '16px' }}
>
</Button>
<Button
size="large"
onClick={handleReset}
disabled={isGenerating}
style={{ minWidth: '140px', height: '44px', fontSize: '16px' }}
>
</Button>
</div>
{/* 内容展示区 */}
{(originalText || correctedText || isGenerating) && (
<Card title="检测结果对比">
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{/* 原始文本 */}
{originalText && (
<div>
<Divider orientation="left" style={{ margin: '0 0 16px 0' }}>
<Text strong style={{ fontSize: '16px', color: '#666' }}>
</Text>
</Divider>
<div
style={{
minHeight: '120px',
padding: '15px',
backgroundColor: '#fafafa',
border: '1px solid #d9d9d9',
borderRadius: '6px',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
fontSize: '14px',
lineHeight: '1.8',
fontFamily: '微软雅黑, 苹方, sans-serif'
}}
>
{originalText}
</div>
</div>
)}
{/* 修正后文本 */}
<div>
<Divider orientation="left" style={{ margin: '0 0 16px 0' }}>
<Text strong style={{ fontSize: '16px', color: '#52c41a' }}>
</Text>
</Divider>
<div
style={{
minHeight: '150px',
padding: '15px',
backgroundColor: isGenerating ? '#fff' : '#f6ffed',
border: `1px solid ${isGenerating ? '#d9d9d9' : '#b7eb8f'}`,
borderRadius: '6px',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
fontSize: '14px',
lineHeight: '1.8',
fontFamily: '微软雅黑, 苹方, sans-serif'
}}
>
{isGenerating ? (
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
...
</div>
) : correctedText ? (
<TextArea
value={correctedText}
onChange={(e) => setCorrectedText(e.target.value)}
bordered={false}
style={{
backgroundColor: 'transparent',
fontSize: '14px',
lineHeight: '1.8',
padding: 0
}}
autoSize={{ minRows: 6 }}
/>
) : (
<div style={{ textAlign: 'center', color: '#999' }}>
</div>
)}
</div>
</div>
</div>
</Card>
)}
</Space>
</div>
)
}
export default SpellCorrect

View File

@@ -30,7 +30,7 @@ const router: RouteObject[] = [
children: [
{
path: '/',
element: LazyLoad(lazy(() => import('@/pages/home')))
element: LazyLoad(lazy(() => import('@/pages/spell-correct')))
},
{
path: '/test1',