From f33e9727f23c43d465ea84749be2e2c17093c05a Mon Sep 17 00:00:00 2001 From: Vibe Studio Date: Fri, 16 Jan 2026 03:55:43 +0000 Subject: [PATCH] Update from Vibe Studio --- package-lock.json | 14 ++ src/api/spellCheck.ts | 40 ++++ src/pages/home/index.tsx | 8 +- src/pages/spell-check/index.tsx | 336 ++++++++++++++++++++++++++++++++ src/router/index.tsx | 4 + 5 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 src/api/spellCheck.ts create mode 100644 src/pages/spell-check/index.tsx diff --git a/package-lock.json b/package-lock.json index 01d2c934..c5635a97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -509,6 +509,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -883,6 +884,7 @@ "resolved": "https://registry.npmmirror.com/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2322,6 +2324,7 @@ "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2337,6 +2340,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2439,6 +2443,7 @@ "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", "license": "MIT", + "peer": true, "dependencies": { "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", @@ -2559,6 +2564,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2950,6 +2956,7 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "license": "MIT", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -3148,6 +3155,7 @@ "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -4144,6 +4152,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4366,6 +4375,7 @@ "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz", "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/async-validator": "^5.0.3", @@ -4874,6 +4884,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4886,6 +4897,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5567,6 +5579,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -5679,6 +5692,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/api/spellCheck.ts b/src/api/spellCheck.ts new file mode 100644 index 00000000..89bef27e --- /dev/null +++ b/src/api/spellCheck.ts @@ -0,0 +1,40 @@ +export interface SpellCheckRequest { + user_input_text: string + auto_correct: boolean + custom_vocab: string[] +} + +export interface DifyRequest { + inputs: { + user_input_text: string + auto_correct: boolean + custom_vocab: string[] + } + query: string + response_mode: string +} + +export function checkAndCorrectSpelling( + user_input_text: string, + auto_correct: boolean, + custom_vocab: string[] +) { + const requestBody: DifyRequest = { + inputs: { + user_input_text, + auto_correct, + custom_vocab + }, + 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-Dmsx84IAGk7rVCko5MWptmK3' + }, + body: JSON.stringify(requestBody) + }) +} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 877f0b3d..a1256d67 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,6 +1,6 @@ import { Card, Row, Col, Typography, Space } from 'antd' import { Link } from 'react-router-dom' -import { RobotOutlined, TranslationOutlined, FileTextOutlined } from '@ant-design/icons' +import { RobotOutlined, TranslationOutlined, FileTextOutlined, EditOutlined } from '@ant-design/icons' const { Title, Paragraph } = Typography @@ -22,6 +22,12 @@ const cards = [ description: 'AI 驱动的翻译工具', icon: , link: '/zh-en-translator' + }, + { + title: '错别字检测', + description: '智能检测和纠正中文文本中的错别字', + icon: , + link: '/spell-check' } ] diff --git a/src/pages/spell-check/index.tsx b/src/pages/spell-check/index.tsx new file mode 100644 index 00000000..50778807 --- /dev/null +++ b/src/pages/spell-check/index.tsx @@ -0,0 +1,336 @@ +import { useState } from 'react' +import { Button, Card, Form, Input, message, Space, Switch } from 'antd' +import { PageHeader } from '@ant-design/pro-components' +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([]) + 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 ( +
+ 正在检测中,请稍候... +
+ ) + } + + if (correctionResults.length === 0) { + return ( +
+ 错别字检测结果将在这里显示 +
+ ) + } + + return ( +
+ {correctionResults.map((item, index) => ( +
+
+ 位置 {item.position}: +
+
+ 原词:{item.original} + + 建议:{item.corrected} +
+ {item.suggestions && item.suggestions.length > 0 && ( +
+ 其他建议:{item.suggestions.join('、')} +
+ )} +
+ ))} +
+ ) + } + + return ( +
+
+

+ 错别字检测与纠正系统 +

+
+ + + +
+ + + + + + + + + + 格式要求:合法的JSON数组,元素为字符串 +
+ } + > + + + + + +
+ + +
+ + +
+ {renderCorrectionResults()} +
+
+ + {correctedText && ( + +
+ {correctedText} +
+
+ )} + + + ) +} + +export default SpellCheck diff --git a/src/router/index.tsx b/src/router/index.tsx index 3885473e..4667030f 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -44,6 +44,10 @@ const router: RouteObject[] = [ path: '/zh-en-translator', element: LazyLoad(lazy(() => import('@/pages/zh-en-translator'))) }, + { + path: '/spell-check', + element: LazyLoad(lazy(() => import('@/pages/spell-check'))) + }, { path: '/404', element: <>404