Marek4321/BabelSlide_2.0
2
1#!/usr/bin/env python32"""3BabelSlide v2.0 - Professional Document Translator4Streamlit application for translating PDF, DOCX, and PPTX documents using AI5"""6 7import streamlit as st8import tempfile9from pathlib import Path10import sys11import os12 13from translators.chatgpt_translator import ChatGPTTranslator14from translators.deepseek_translator import DeepSeekTranslator15from processors.pdf_processor import PDFProcessor16from processors.docx_processor import DOCXProcessor17from processors.pptx_processor import PPTXProcessor18from utils.constants import LANGUAGES, API_PROVIDERS19from utils.validator import FileValidator20from utils.logger import setup_logger, ProcessLogger21from core.exceptions import (22 BabelSlideException, 23 ValidationError, 24 UnsupportedFileError, 25 FileSizeError,26 APIKeyError,27 TranslationError,28 ProcessorError29)30 31class BabelSlideStreamlitApp:32 """Streamlit interface for BabelSlide application"""33 34 def __init__(self):35 self.logger = setup_logger("BabelSlideUI")36 self.process_logger = ProcessLogger(self.logger)37 38 # Initialize session state39 if 'processing' not in st.session_state:40 st.session_state.processing = False41 if 'translation_result' not in st.session_state:42 st.session_state.translation_result = None43 if 'review_result' not in st.session_state:44 st.session_state.review_result = None45 46 def setup_page_config(self):47 """Configure Streamlit page"""48 st.set_page_config(49 page_title="BabelSlide - Document Translator",50 page_icon="๐",51 layout="wide",52 initial_sidebar_state="expanded"53 )54 55 # Custom CSS56 st.markdown("""57 <style>58 .main-header {59 text-align: center;60 padding: 2rem 0;61 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);62 color: white;63 border-radius: 12px;64 margin-bottom: 2rem;65 }66 67 .success-box {68 background: #d1fae5;69 border: 1px solid #10b981;70 border-radius: 8px;71 padding: 1rem;72 margin: 1rem 0;73 }74 75 .error-box {76 background: #fef2f2;77 border: 1px solid #ef4444;78 border-radius: 8px;79 padding: 1rem;80 margin: 1rem 0;81 }82 83 .info-box {84 background: #eff6ff;85 border: 1px solid #3b82f6;86 border-radius: 8px;87 padding: 1rem;88 margin: 1rem 0;89 }90 91 .stButton > button {92 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);93 color: white;94 border: none;95 border-radius: 8px;96 padding: 0.5rem 2rem;97 font-weight: 600;98 }99 </style>100 """, unsafe_allow_html=True)101 102 def render_header(self):103 """Render application header"""104 st.markdown("""105 <div class="main-header">106 <h1>๐ BabelSlide v2.0</h1>107 <p>Professional Document Translation using AI โข PDF โข DOCX โข PPTX</p>108 </div>109 """, unsafe_allow_html=True)110 111 def render_sidebar(self):112 """Render configuration sidebar"""113 st.sidebar.markdown("## โ๏ธ Configuration")114 115 # API Provider116 api_provider = st.sidebar.selectbox(117 "AI Provider",118 options=list(API_PROVIDERS.keys()),119 index=0,120 help="Choose your preferred translation AI"121 )122 123 # API Key124 api_key = st.sidebar.text_input(125 "API Key",126 type="password",127 placeholder="Enter your API key (sk-... for OpenAI)",128 help="Your API key is never stored permanently"129 )130 131 st.sidebar.markdown("---")132 133 # Languages134 col1, col2 = st.sidebar.columns(2)135 136 with col1:137 source_lang = st.selectbox(138 "Source Language",139 options=list(LANGUAGES.keys()),140 index=list(LANGUAGES.keys()).index("English"),141 help="Language of the original document"142 )143 144 with col2:145 target_lang = st.selectbox(146 "Target Language",147 options=list(LANGUAGES.keys()),148 index=list(LANGUAGES.keys()).index("Polish"),149 help="Language to translate to"150 )151 152 st.sidebar.markdown("---")153 st.sidebar.markdown("### ๐ Supported Formats")154 st.sidebar.info("โข PDF documents\nโข DOCX (Word) files\nโข PPTX (PowerPoint) presentations")155 st.sidebar.warning("Maximum file size: 50 MB")156 157 return api_provider, api_key, source_lang, target_lang158 159 def render_file_upload(self):160 """Render file upload section"""161 st.markdown("## ๐ Document Upload")162 163 uploaded_file = st.file_uploader(164 "Choose a document to translate",165 type=['pdf', 'docx', 'pptx'],166 help="Upload PDF, DOCX, or PPTX files (max 50 MB)",167 accept_multiple_files=False168 )169 170 if uploaded_file:171 col1, col2, col3 = st.columns([2, 1, 1])172 with col1:173 st.info(f"๐ **File:** {uploaded_file.name}")174 with col2:175 file_size = len(uploaded_file.getvalue()) / (1024 * 1024)176 st.info(f"๐ **Size:** {file_size:.1f} MB")177 with col3:178 file_type = uploaded_file.name.split('.')[-1].upper()179 st.info(f"๐ **Type:** {file_type}")180 181 return uploaded_file182 183 def validate_inputs(self, file, api_provider, api_key, source_lang, target_lang):184 """Validate all inputs before processing"""185 errors = []186 187 if not file:188 errors.append("Please upload a document")189 190 if not api_key or not api_key.strip():191 errors.append("Please provide an API key")192 193 if source_lang == target_lang:194 errors.append("Source and target languages must be different")195 196 # Validate file if provided197 if file:198 try:199 # Create temporary file for validation200 with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file.name.split('.')[-1]}") as tmp_file:201 tmp_file.write(file.getvalue())202 tmp_file_path = Path(tmp_file.name)203 204 FileValidator.validate_file(tmp_file_path)205 tmp_file_path.unlink() # Clean up206 207 except (ValidationError, UnsupportedFileError, FileSizeError) as e:208 errors.append(f"File validation error: {str(e)}")209 210 # Validate API key format211 try:212 if api_key:213 FileValidator.validate_api_key(api_key.strip(), api_provider)214 except ValidationError as e:215 errors.append(f"API key error: {str(e)}")216 217 return errors218 219 def process_document(self, file, api_provider, api_key, source_lang, target_lang):220 """Process document translation"""221 try:222 # Create temporary file223 with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file.name.split('.')[-1]}") as tmp_file:224 tmp_file.write(file.getvalue())225 tmp_file_path = Path(tmp_file.name)226 227 # Create translator228 if api_provider == "ChatGPT":229 translator = ChatGPTTranslator(api_key.strip())230 elif api_provider == "DeepSeek":231 translator = DeepSeekTranslator(api_key.strip())232 else:233 raise ValueError(f"Unsupported provider: {api_provider}")234 235 # Create processor based on file extension236 extension = tmp_file_path.suffix.lower()237 if extension == '.pdf':238 processor = PDFProcessor(translator)239 elif extension == '.docx':240 processor = DOCXProcessor(translator)241 elif extension == '.pptx':242 processor = PPTXProcessor(translator)243 else:244 raise ValueError(f"Unsupported file format: {extension}")245 246 # Progress tracking247 progress_bar = st.progress(0)248 status_text = st.empty()249 250 def progress_callback(progress_val, desc):251 progress_bar.progress(progress_val)252 status_text.text(desc)253 254 # Process document255 status_text.text("Starting translation...")256 output_path, summary_text = processor.process_document(257 tmp_file_path,258 source_lang,259 target_lang,260 progress_callback261 )262 263 # Generate review264 status_text.text("Generating review...")265 review_text = self.generate_review(summary_text, source_lang, translator)266 267 # Clean up temp file268 tmp_file_path.unlink()269 270 progress_bar.progress(1.0)271 status_text.text("โ
Translation completed!")272 273 return output_path, review_text, summary_text274 275 except Exception as e:276 self.logger.error(f"Translation error: {str(e)}")277 raise278 279 def generate_review(self, translated_text: str, source_lang: str, translator) -> str:280 """Generate translation review"""281 try:282 system_prompt = f"""Generate a comprehensive translation review in {source_lang} covering:283 1. Translation quality assessment284 2. Coherence and consistency285 3. Technical terminology accuracy 286 4. Overall readability287 5. Recommendations for improvement288 289 Keep the review concise but informative."""290 291 # Use translator's API to generate review292 review = translator._make_translation_request(293 f"Review this translated text:\n\n{translated_text[:2000]}...",294 "English",295 source_lang296 )297 298 return translator._clean_translation_output(review)299 300 except Exception as e:301 return f"Review generation failed: {str(e)}"302 303 def render_results(self):304 """Render translation results"""305 if st.session_state.translation_result:306 st.markdown("## ๐ฅ Results")307 308 col1, col2 = st.columns(2)309 310 with col1:311 st.markdown("### ๐ Translated Document")312 if st.session_state.translation_result:313 with open(st.session_state.translation_result, 'rb') as file:314 st.download_button(315 label="โฌ๏ธ Download Translated Document",316 data=file.read(),317 file_name=Path(st.session_state.translation_result).name,318 mime="application/octet-stream"319 )320 321 with col2:322 st.markdown("### ๐ Translation Review")323 if st.session_state.review_result:324 st.download_button(325 label="โฌ๏ธ Download Review",326 data=st.session_state.review_result,327 file_name="translation_review.txt",328 mime="text/plain"329 )330 331 # Summary332 if hasattr(st.session_state, 'summary_text') and st.session_state.summary_text:333 st.markdown("### ๐ Translation Summary")334 with st.expander("View Summary", expanded=False):335 st.text_area(336 "Summary",337 value=st.session_state.summary_text[:1000] + "..." if len(st.session_state.summary_text) > 1000 else st.session_state.summary_text,338 height=200,339 disabled=True,340 label_visibility="collapsed"341 )342 343 def run(self):344 """Main application loop"""345 self.setup_page_config()346 self.render_header()347 348 # Sidebar configuration349 api_provider, api_key, source_lang, target_lang = self.render_sidebar()350 351 # Main content352 uploaded_file = self.render_file_upload()353 354 # Translation button355 st.markdown("---")356 col1, col2, col3 = st.columns([1, 2, 1])357 with col2:358 translate_button = st.button(359 "๐ Translate Document",360 disabled=st.session_state.processing,361 use_container_width=True362 )363 364 # Process translation365 if translate_button:366 # Validate inputs367 errors = self.validate_inputs(uploaded_file, api_provider, api_key, source_lang, target_lang)368 369 if errors:370 st.error("โ **Please fix the following errors:**")371 for error in errors:372 st.error(f"โข {error}")373 else:374 st.session_state.processing = True375 376 try:377 with st.spinner("Translating document..."):378 output_path, review_text, summary_text = self.process_document(379 uploaded_file, api_provider, api_key, source_lang, target_lang380 )381 382 # Store results383 st.session_state.translation_result = output_path384 st.session_state.review_result = review_text385 st.session_state.summary_text = summary_text386 387 st.success(f"โ
**Translation completed successfully!**\n\n"388 f"๐ **File:** {uploaded_file.name}\n"389 f"๐ **Translation:** {source_lang} โ {target_lang}\n"390 f"๐ค **Provider:** {api_provider}")391 392 # Auto-refresh to show results393 st.rerun()394 395 except Exception as e:396 st.error(f"โ **Translation failed:** {str(e)}")397 398 finally:399 st.session_state.processing = False400 401 # Show results if available402 self.render_results()403 404 # Footer405 st.markdown("---")406 st.markdown(407 "<div style='text-align: center; color: #666;'>"408 "<strong>BabelSlide v2.0</strong> โข Professional document translation โข Built for global communication"409 "</div>",410 unsafe_allow_html=True411 )412 413# Main entry point414if __name__ == "__main__":415 app = BabelSlideStreamlitApp()416 app.run()