'use client' import { useState, useRef } from 'react' import { ArrowRightLeft, Copy, Sparkles, StopCircle, Check } from './icons' import { Button } from './ui/button' import { Textarea } from './ui/textarea' import { Card } from './ui/card' import { cn } from '../lib/utils' import LanguageSelect from './LanguageSelect' import { AUTO_LANGUAGE, COMMON_LANGUAGE_OPTIONS, getLanguageName, LANGUAGE_OPTIONS, } from '@/lib/languages' const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000' const STYLES = [ { value: 'literal', name: '直译', description: '保留原文结构' }, { value: 'fluent', name: '意译', description: '自然流畅' }, { value: 'casual', name: '口语', description: '轻松对话' }, ] export default function TranslatorForm() { const [sourceText, setSourceText] = useState('') const [translation, setTranslation] = useState('') const [sourceLang, setSourceLang] = useState('auto') const [targetLang, setTargetLang] = useState('zh') const [style, setStyle] = useState('literal') const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') const [copied, setCopied] = useState(false) const abortRef = useRef(null) const handleTranslate = async () => { if (!sourceText.trim()) return if (abortRef.current) { abortRef.current.abort() } abortRef.current = new AbortController() setIsLoading(true) setError('') setTranslation('') try { const res = await fetch(`${API_BASE}/api/v1/translate/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source_text: sourceText, source_lang: sourceLang, source_lang_name: getLanguageName(sourceLang), target_lang: targetLang, target_lang_name: getLanguageName(targetLang), style, }), signal: abortRef.current.signal, }) if (!res.ok) { throw new Error('翻译请求失败') } const reader = res.body?.getReader() const decoder = new TextDecoder() let buffer = '' while (reader) { const { value, done } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const parts = buffer.split('\n\n') buffer = parts.pop() ?? '' for (const p of parts) { const lines = p.split('\n') const dataLine = lines.find(l => l.startsWith('data:')) if (dataLine) { const data = JSON.parse(dataLine.slice(5).trim()) if (data.delta) { setTranslation(prev => prev + data.delta) } } } } } catch (err: any) { if (err.name !== 'AbortError') { setError(err.message || '翻译失败') } } finally { setIsLoading(false) } } const handleStop = () => { if (abortRef.current) { abortRef.current.abort() setIsLoading(false) } } const handleCopy = () => { navigator.clipboard.writeText(translation) setCopied(true) setTimeout(() => setCopied(false), 2000) } const handleSwapLanguages = () => { if (sourceLang === 'auto') return setSourceLang(targetLang) setTargetLang(sourceLang) setSourceText(translation) setTranslation(sourceText) } return (
{/* Controls Bar */}
{STYLES.map((s) => ( ))}
{isLoading ? ( ) : ( )}
{/* Main Input/Output Area */}
{/* Source Text */}