217 lines
6.6 KiB
TypeScript
217 lines
6.6 KiB
TypeScript
import { useState } from 'react'
|
||
import { Button, Card, Form, Input, message, Space } from 'antd'
|
||
import { PageHeader } from '@ant-design/pro-components'
|
||
import { translateChineseToEnglish } from '@/api/zh-en-translator'
|
||
|
||
const ZhEnTranslator = () => {
|
||
const [form] = Form.useForm()
|
||
const [loading, setLoading] = useState(false)
|
||
const [translationResult, setTranslationResult] = useState('')
|
||
const [currentInput, setCurrentInput] = useState('')
|
||
const maxChars = 5000
|
||
|
||
const handleTranslate = async () => {
|
||
try {
|
||
const source_content = form.getFieldValue('source_content') || ''
|
||
if (!source_content.trim()) {
|
||
message.warning('请输入要翻译的中文内容')
|
||
return
|
||
}
|
||
|
||
setLoading(true)
|
||
setTranslationResult('')
|
||
|
||
const response = await translateChineseToEnglish(source_content)
|
||
|
||
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('翻译完成')
|
||
setLoading(false)
|
||
return
|
||
}
|
||
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
|
||
setTranslationResult(fullContent)
|
||
} else if (parsed.event === 'error') {
|
||
throw new Error(parsed.message || 'Dify API 返回错误')
|
||
}
|
||
} catch (parseError) {
|
||
console.warn('跳过无法解析的行:', trimmedLine)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} finally {
|
||
reader.releaseLock()
|
||
}
|
||
|
||
if (fullContent) {
|
||
message.success('翻译完成')
|
||
} else {
|
||
throw new Error('未收到翻译结果')
|
||
}
|
||
} catch (error) {
|
||
console.error('翻译错误:', error)
|
||
message.error(error instanceof Error ? error.message : '翻译失败,请稍后重试')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleClear = () => {
|
||
form.resetFields()
|
||
setTranslationResult('')
|
||
setCurrentInput('')
|
||
message.info('已清空内容')
|
||
}
|
||
|
||
const handleCopy = async () => {
|
||
if (!translationResult) {
|
||
message.warning('没有可复制的内容')
|
||
return
|
||
}
|
||
|
||
try {
|
||
await navigator.clipboard.writeText(translationResult)
|
||
message.success('复制成功')
|
||
} catch (error) {
|
||
console.error('复制失败:', error)
|
||
message.error('复制失败,请手动复制')
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px' }}>
|
||
<PageHeader
|
||
title="中文转英文翻译助手"
|
||
subTitle="专业的中文到英文文本翻译工具,支持流式输出,翻译结果地道自然"
|
||
style={{ marginBottom: '30px' }}
|
||
/>
|
||
|
||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||
<Card title="参数输入区">
|
||
<Form form={form} layout="vertical">
|
||
<Form.Item
|
||
name="source_content"
|
||
label={
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
||
<span>中文原文</span>
|
||
<span style={{ color: currentInput.length > maxChars ? '#ff4d4f' : '#999' }}>
|
||
{currentInput.length} / {maxChars}
|
||
</span>
|
||
</div>
|
||
}
|
||
rules={[
|
||
{ required: true, message: '请输入要翻译的中文内容' },
|
||
{ max: maxChars, message: `输入内容不能超过${maxChars}个字符` }
|
||
]}
|
||
>
|
||
<Input.TextArea
|
||
placeholder="请输入需要翻译的中文内容(最多5000个字符)"
|
||
rows={8}
|
||
value={currentInput}
|
||
onChange={(e) => setCurrentInput(e.target.value)}
|
||
showCount
|
||
maxLength={maxChars}
|
||
style={{ fontSize: '14px' }}
|
||
/>
|
||
</Form.Item>
|
||
</Form>
|
||
</Card>
|
||
|
||
<Space style={{ width: '100%', justifyContent: 'center' }} size="middle">
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
onClick={handleTranslate}
|
||
loading={loading}
|
||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||
>
|
||
翻译
|
||
</Button>
|
||
<Button
|
||
size="large"
|
||
onClick={handleClear}
|
||
disabled={loading}
|
||
style={{ minWidth: '120px', height: '40px', fontSize: '16px' }}
|
||
>
|
||
清空
|
||
</Button>
|
||
</Space>
|
||
|
||
<Card
|
||
title="翻译结果"
|
||
extra={
|
||
translationResult && (
|
||
<Button onClick={handleCopy} size="small">
|
||
复制
|
||
</Button>
|
||
)
|
||
}
|
||
>
|
||
<div
|
||
style={{
|
||
minHeight: '150px',
|
||
maxHeight: '400px',
|
||
overflowY: 'auto',
|
||
padding: '15px',
|
||
backgroundColor: '#fafafa',
|
||
border: '1px solid #d9d9d9',
|
||
borderRadius: '4px',
|
||
whiteSpace: 'pre-wrap',
|
||
wordWrap: 'break-word',
|
||
fontSize: '14px',
|
||
lineHeight: '1.6'
|
||
}}
|
||
>
|
||
{loading ? (
|
||
<div style={{ textAlign: 'center', color: '#999', padding: '20px' }}>
|
||
正在翻译中,请稍候...
|
||
</div>
|
||
) : translationResult || (
|
||
<div style={{ textAlign: 'center', color: '#999' }}>
|
||
翻译结果将在这里显示
|
||
</div>
|
||
)}
|
||
</div>
|
||
</Card>
|
||
</Space>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default ZhEnTranslator
|