Files
copilot-toolbox-template3344/src/pages/zh-en-translator/index.tsx

217 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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