BIAF-offASR / frontend /src /App.jsx
froster02's picture
refactor: remove login page and auth system
b7af188
import { useState, useEffect, useRef } from 'react';
import Navbar from './components/Navbar';
import DashboardTab from './components/DashboardTab';
import TextTranslationTab from './components/TextTranslationTab';
import DocumentTranslationTab from './components/DocumentTranslationTab';
import AudioTranslationTab from './components/AudioTranslationTab';
import VideoTranslationTab from './components/VideoTranslationTab';
import SettingsTab from './components/SettingsTab';
import { LanguageProvider, useLang } from './LanguageContext';
function AppInner() {
const { t } = useLang();
const [activeTab, setActiveTab] = useState('dashboard');
const [modelsStatus, setModelsStatus] = useState({
is_cached: false,
whisper_cached: false,
nllb_cached: false,
tts_cached: false,
models_dir: ''
});
const [isConnected, setIsConnected] = useState(true);
const [whisperSize, setWhisperSize] = useState('base');
// Text Translation States
const [textInput, setTextInput] = useState('');
const [textOutput, setTextOutput] = useState('');
const [textSrcLang, setTextSrcLang] = useState('auto');
const [textTgtLang, setTextTgtLang] = useState('Hindi');
const [detectedTextLang, setDetectedTextLang] = useState('');
const [isTranslatingText, setIsTranslatingText] = useState(false);
const [ttsAudioUrl, setTtsAudioUrl] = useState('');
const [isGeneratingTts, setIsGeneratingTts] = useState(false);
// Audio Translation States
const [audioFile, setAudioFile] = useState(null);
const [audioSrcLang, setAudioSrcLang] = useState('auto');
const [audioTgtLang, setAudioTgtLang] = useState('Hindi');
const [isProcessingAudio, setIsProcessingAudio] = useState(false);
const [audioProgress, setAudioProgress] = useState(0);
const [audioProgressText, setAudioProgressText] = useState('');
const [audioResult, setAudioResult] = useState(null);
const [audioActiveSubTab, setAudioActiveSubTab] = useState('translation');
const [isRecording, setIsRecording] = useState(false);
const [recordingTime, setRecordingTime] = useState(0);
const mediaRecorderRef = useRef(null);
const audioChunksRef = useRef([]);
const timerRef = useRef(null);
// Video Translation States
const [videoFile, setVideoFile] = useState(null);
const [videoSrcLang, setVideoSrcLang] = useState('auto');
const [videoTgtLang, setVideoTgtLang] = useState('Hindi');
const [burnSubtitles, setBurnSubtitles] = useState(true);
const [overlayVoice, setOverlayVoice] = useState(false);
const [isProcessingVideo, setIsProcessingVideo] = useState(false);
const [videoProgress, setVideoProgress] = useState(0);
const [videoProgressText, setVideoProgressText] = useState('');
const [videoResult, setVideoResult] = useState(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
// Document Translation States
const [docFile, setDocFile] = useState(null);
const [docSrcLang, setDocSrcLang] = useState('auto');
const [docTgtLang, setDocTgtLang] = useState('Hindi');
const [isProcessingDoc, setIsProcessingDoc] = useState(false);
const [docResult, setDocResult] = useState(null);
const pageTitles = {
dashboard: t('page.dashboard'),
text: t('page.text'),
docs: t('page.docs'),
audio: t('page.audio'),
video: t('page.video'),
settings: t('page.settings'),
};
const pageSubtitles = {
dashboard: t('page.dashboardSub'),
text: t('page.textSub'),
docs: t('page.docsSub'),
audio: t('page.audioSub'),
video: t('page.videoSub'),
settings: t('page.settingsSub'),
};
const checkServerStatus = async () => {
try {
const res = await fetch('/api/models-status');
if (res.ok) {
const data = await res.json();
setModelsStatus(data);
setIsConnected(true);
} else {
setIsConnected(false);
}
} catch {
setIsConnected(false);
}
};
useEffect(() => {
let cancelled = false;
const interval = setInterval(async () => {
try {
const res = await fetch('/api/models-status');
if (!cancelled) {
if (res.ok) {
const data = await res.json();
setModelsStatus(data);
setIsConnected(true);
} else {
setIsConnected(false);
}
}
} catch {
if (!cancelled) setIsConnected(false);
}
}, 5000);
return () => { cancelled = true; clearInterval(interval); };
}, []);
const handleTextTranslate = async () => {
if (!textInput.trim()) return;
setIsTranslatingText(true);
setTtsAudioUrl('');
try {
const res = await fetch('/api/translate-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: textInput,
src_lang: textSrcLang,
tgt_lang: textTgtLang
})
});
if (res.ok) {
const data = await res.json();
setTextOutput(data.translated_text);
if (data.detected_src_lang) {
setDetectedTextLang(data.detected_src_lang);
}
} else {
alert('Translation failed. Please make sure the backend is running and models are loaded.');
}
} catch {
alert('Network error connecting to backend.');
} finally {
setIsTranslatingText(false);
}
};
const handleTextToSpeech = async () => {
if (!textOutput.trim()) return;
setIsGeneratingTts(true);
try {
const res = await fetch('/api/text-to-speech', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: textOutput,
lang: textTgtLang
})
});
if (res.ok) {
const blob = await res.blob();
const audioUrl = URL.createObjectURL(blob);
setTtsAudioUrl(audioUrl);
} else {
alert('TTS Synthesis failed.');
}
} catch {
alert('Error generating TTS.');
} finally {
setIsGeneratingTts(false);
}
};
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorderRef.current = new MediaRecorder(stream);
audioChunksRef.current = [];
mediaRecorderRef.current.ondataavailable = (e) => {
if (e.data.size > 0) audioChunksRef.current.push(e.data);
};
mediaRecorderRef.current.onstop = () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/wav' });
const file = new File([audioBlob], "recorded_audio.wav", { type: 'audio/wav' });
setAudioFile(file);
setAudioResult(null);
stream.getTracks().forEach(track => track.stop());
};
mediaRecorderRef.current.start();
setIsRecording(true);
setRecordingTime(0);
timerRef.current = setInterval(() => {
setRecordingTime(prev => prev + 1);
}, 1000);
} catch (err) {
alert('Could not access microphone: ' + err.message);
}
};
const stopRecording = () => {
if (mediaRecorderRef.current && isRecording) {
mediaRecorderRef.current.stop();
setIsRecording(false);
clearInterval(timerRef.current);
}
};
const handleAudioUpload = (e) => {
const file = e.target.files[0];
if (file) {
setAudioFile(file);
setAudioResult(null);
}
};
const processAudio = async () => {
if (!audioFile) return;
setIsProcessingAudio(true);
setAudioProgress(10);
setAudioProgressText('Uploading audio file and initializing pipeline...');
const formData = new FormData();
formData.append('file', audioFile);
formData.append('model_size', whisperSize);
formData.append('src_lang', audioSrcLang);
formData.append('tgt_lang', audioTgtLang);
try {
const progressInterval = setInterval(() => {
setAudioProgress((prev) => {
if (prev >= 90) { clearInterval(progressInterval); return prev; }
if (prev >= 70) { setAudioProgressText('Translating text segments and synthesizing dubbing audio...'); return prev + 1; }
if (prev >= 40) { setAudioProgressText('Transcribing speech offline using Whisper ASR...'); return prev + 2; }
return prev + 5;
});
}, 800);
const res = await fetch('/api/translate-audio', {
method: 'POST',
body: formData
});
clearInterval(progressInterval);
if (res.ok) {
setAudioProgress(100);
setAudioProgressText('Synthesis complete!');
const data = await res.json();
setAudioResult(data);
} else {
alert('Audio processing failed. Check backend logs.');
setIsProcessingAudio(false);
}
} catch {
alert('Error connecting to backend.');
setIsProcessingAudio(false);
} finally {
setTimeout(() => {
setIsProcessingAudio(false);
setAudioProgress(0);
}, 1000);
}
};
const handleVideoUpload = (e) => {
const file = e.target.files[0];
if (file) {
setVideoFile(file);
setVideoResult(null);
}
};
const pollJob = async (job_id, onSuccess, onFail, onProgress) => {
const interval = setInterval(async () => {
try {
const res = await fetch(`/api/jobs/${job_id}`);
if (res.ok) {
const job = await res.json();
if (onProgress) onProgress(job.progress, job.status);
if (job.status === 'completed') {
clearInterval(interval);
onSuccess(job.result);
} else if (job.status === 'failed') {
clearInterval(interval);
onFail(job.error || 'Job failed');
}
} else {
clearInterval(interval);
onFail('Error checking job status');
}
} catch {
clearInterval(interval);
onFail('Network error checking job status');
}
}, 2000);
return interval;
};
const processVideo = async () => {
if (!videoFile) return;
setIsProcessingVideo(true);
setVideoProgress(5);
setVideoProgressText('Uploading video track. Initializing workspace...');
const formData = new FormData();
formData.append('file', videoFile);
formData.append('model_size', whisperSize);
formData.append('src_lang', videoSrcLang);
formData.append('tgt_lang', videoTgtLang);
formData.append('burn_subtitles_option', burnSubtitles);
formData.append('overlay_voice_option', overlayVoice);
try {
const res = await fetch('/api/process-video', {
method: 'POST',
body: formData
});
if (res.ok) {
const { job_id } = await res.json();
pollJob(
job_id,
(result) => {
setVideoResult(result);
setVideoProgress(100);
setVideoProgressText('Video processed successfully!');
setTimeout(() => {
setIsProcessingVideo(false);
setVideoProgress(0);
}, 1000);
},
(error) => {
alert(`Video processing failed: ${error}`);
setIsProcessingVideo(false);
},
(progress, status) => {
setVideoProgress(progress);
if (status === 'processing') {
if (progress < 40) setVideoProgressText('Running speech-to-text extraction using Whisper...');
else if (progress < 70) setVideoProgressText('Translating timeline and rendering transcript overlays...');
else setVideoProgressText('Injecting subtitle layers and copying streams with FFmpeg...');
}
}
);
} else {
alert('Video processing failed to start.');
setIsProcessingVideo(false);
}
} catch {
alert('Error connecting to video processing endpoint.');
setIsProcessingVideo(false);
}
};
const handleDocUpload = (e) => {
const file = e.target.files[0];
if (file) {
setDocFile(file);
setDocResult(null);
}
};
const processDoc = async () => {
if (!docFile) return;
setIsProcessingDoc(true);
const formData = new FormData();
formData.append('file', docFile);
formData.append('src_lang', docSrcLang);
formData.append('tgt_lang', docTgtLang);
try {
const res = await fetch('/api/translate-document', {
method: 'POST',
body: formData
});
if (res.ok) {
const { job_id } = await res.json();
pollJob(
job_id,
(result) => {
setDocResult(result);
setIsProcessingDoc(false);
},
(error) => {
alert(`Document translation failed: ${error}`);
setIsProcessingDoc(false);
}
);
} else {
alert('Document translation failed to start.');
setIsProcessingDoc(false);
}
} catch {
alert('Error connecting to backend.');
setIsProcessingDoc(false);
}
};
return (
<div className="app-container">
<Navbar
activeTab={activeTab}
setActiveTab={setActiveTab}
isMenuOpen={isMenuOpen}
setIsMenuOpen={setIsMenuOpen}
isConnected={isConnected}
/>
<div className="main-content">
<div className="header-container">
<h1 className="page-title">{pageTitles[activeTab]}</h1>
<p className="page-subtitle">{pageSubtitles[activeTab]}</p>
</div>
{activeTab === 'dashboard' && <DashboardTab setActiveTab={setActiveTab} />}
{activeTab === 'text' && (
<TextTranslationTab
textInput={textInput} setTextInput={setTextInput}
textOutput={textOutput}
textSrcLang={textSrcLang} setTextSrcLang={setTextSrcLang}
textTgtLang={textTgtLang} setTextTgtLang={setTextTgtLang}
detectedTextLang={detectedTextLang}
isTranslatingText={isTranslatingText} handleTextTranslate={handleTextTranslate}
ttsAudioUrl={ttsAudioUrl}
isGeneratingTts={isGeneratingTts} handleTextToSpeech={handleTextToSpeech}
/>
)}
{activeTab === 'docs' && (
<DocumentTranslationTab
docFile={docFile} handleDocUpload={handleDocUpload}
docSrcLang={docSrcLang} setDocSrcLang={setDocSrcLang}
docTgtLang={docTgtLang} setDocTgtLang={setDocTgtLang}
isProcessingDoc={isProcessingDoc} processDoc={processDoc}
docResult={docResult}
/>
)}
{activeTab === 'audio' && (
<AudioTranslationTab
audioFile={audioFile} handleAudioUpload={handleAudioUpload}
audioSrcLang={audioSrcLang} setAudioSrcLang={setAudioSrcLang}
audioTgtLang={audioTgtLang} setAudioTgtLang={setAudioTgtLang}
isProcessingAudio={isProcessingAudio} processAudio={processAudio}
audioProgress={audioProgress} audioProgressText={audioProgressText}
audioResult={audioResult}
audioActiveSubTab={audioActiveSubTab} setAudioActiveSubTab={setAudioActiveSubTab}
isRecording={isRecording} startRecording={startRecording} stopRecording={stopRecording}
recordingTime={recordingTime}
/>
)}
{activeTab === 'video' && (
<VideoTranslationTab
videoFile={videoFile} handleVideoUpload={handleVideoUpload}
videoSrcLang={videoSrcLang} setVideoSrcLang={setVideoSrcLang}
videoTgtLang={videoTgtLang} setVideoTgtLang={setVideoTgtLang}
burnSubtitles={burnSubtitles} setBurnSubtitles={setBurnSubtitles}
overlayVoice={overlayVoice} setOverlayVoice={setOverlayVoice}
isProcessingVideo={isProcessingVideo} processVideo={processVideo}
videoProgress={videoProgress} videoProgressText={videoProgressText}
videoResult={videoResult}
/>
)}
{activeTab === 'settings' && (
<SettingsTab
whisperSize={whisperSize} setWhisperSize={setWhisperSize}
isConnected={isConnected} modelsStatus={modelsStatus}
checkServerStatus={checkServerStatus}
/>
)}
</div>
<footer className="app-footer">
<div className="footer-brand">
<span>🌾</span>
{t('footer.brand')}
</div>
<div className="footer-copy">© {new Date().getFullYear()} {t('footer.copyright')}</div>
</footer>
</div>
);
}
function App() {
return (
<LanguageProvider>
<AppInner />
</LanguageProvider>
);
}
export default App;