#!/usr/bin/env python3
"""
BabelSlide v2.0 - Professional Document Translator
Streamlit application for translating PDF, DOCX, and PPTX documents using AI
"""
import streamlit as st
import tempfile
from pathlib import Path
import sys
import os
from translators.chatgpt_translator import ChatGPTTranslator
from translators.deepseek_translator import DeepSeekTranslator
from processors.pdf_processor import PDFProcessor
from processors.docx_processor import DOCXProcessor
from processors.pptx_processor import PPTXProcessor
from utils.constants import LANGUAGES, API_PROVIDERS
from utils.validator import FileValidator
from utils.logger import setup_logger, ProcessLogger
from core.exceptions import (
BabelSlideException,
ValidationError,
UnsupportedFileError,
FileSizeError,
APIKeyError,
TranslationError,
ProcessorError
)
class BabelSlideStreamlitApp:
"""Streamlit interface for BabelSlide application"""
def __init__(self):
self.logger = setup_logger("BabelSlideUI")
self.process_logger = ProcessLogger(self.logger)
# Initialize session state
if 'processing' not in st.session_state:
st.session_state.processing = False
if 'translation_result' not in st.session_state:
st.session_state.translation_result = None
if 'review_result' not in st.session_state:
st.session_state.review_result = None
def setup_page_config(self):
"""Configure Streamlit page"""
st.set_page_config(
page_title="BabelSlide - Document Translator",
page_icon="š",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS
st.markdown("""
""", unsafe_allow_html=True)
def render_header(self):
"""Render application header"""
st.markdown("""
š BabelSlide v2.0
Professional Document Translation using AI ⢠PDF ⢠DOCX ⢠PPTX
""", unsafe_allow_html=True)
def render_sidebar(self):
"""Render configuration sidebar"""
st.sidebar.markdown("## āļø Configuration")
# API Provider
api_provider = st.sidebar.selectbox(
"AI Provider",
options=list(API_PROVIDERS.keys()),
index=0,
help="Choose your preferred translation AI"
)
# API Key
api_key = st.sidebar.text_input(
"API Key",
type="password",
placeholder="Enter your API key (sk-... for OpenAI)",
help="Your API key is never stored permanently"
)
st.sidebar.markdown("---")
# Languages
col1, col2 = st.sidebar.columns(2)
with col1:
source_lang = st.selectbox(
"Source Language",
options=list(LANGUAGES.keys()),
index=list(LANGUAGES.keys()).index("English"),
help="Language of the original document"
)
with col2:
target_lang = st.selectbox(
"Target Language",
options=list(LANGUAGES.keys()),
index=list(LANGUAGES.keys()).index("Polish"),
help="Language to translate to"
)
st.sidebar.markdown("---")
st.sidebar.markdown("### š Supported Formats")
st.sidebar.info("⢠PDF documents\n⢠DOCX (Word) files\n⢠PPTX (PowerPoint) presentations")
st.sidebar.warning("Maximum file size: 50 MB")
return api_provider, api_key, source_lang, target_lang
def render_file_upload(self):
"""Render file upload section"""
st.markdown("## š Document Upload")
uploaded_file = st.file_uploader(
"Choose a document to translate",
type=['pdf', 'docx', 'pptx'],
help="Upload PDF, DOCX, or PPTX files (max 50 MB)",
accept_multiple_files=False
)
if uploaded_file:
col1, col2, col3 = st.columns([2, 1, 1])
with col1:
st.info(f"š **File:** {uploaded_file.name}")
with col2:
file_size = len(uploaded_file.getvalue()) / (1024 * 1024)
st.info(f"š **Size:** {file_size:.1f} MB")
with col3:
file_type = uploaded_file.name.split('.')[-1].upper()
st.info(f"š **Type:** {file_type}")
return uploaded_file
def validate_inputs(self, file, api_provider, api_key, source_lang, target_lang):
"""Validate all inputs before processing"""
errors = []
if not file:
errors.append("Please upload a document")
if not api_key or not api_key.strip():
errors.append("Please provide an API key")
if source_lang == target_lang:
errors.append("Source and target languages must be different")
# Validate file if provided
if file:
try:
# Create temporary file for validation
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file.name.split('.')[-1]}") as tmp_file:
tmp_file.write(file.getvalue())
tmp_file_path = Path(tmp_file.name)
FileValidator.validate_file(tmp_file_path)
tmp_file_path.unlink() # Clean up
except (ValidationError, UnsupportedFileError, FileSizeError) as e:
errors.append(f"File validation error: {str(e)}")
# Validate API key format
try:
if api_key:
FileValidator.validate_api_key(api_key.strip(), api_provider)
except ValidationError as e:
errors.append(f"API key error: {str(e)}")
return errors
def process_document(self, file, api_provider, api_key, source_lang, target_lang):
"""Process document translation"""
try:
# Create temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file.name.split('.')[-1]}") as tmp_file:
tmp_file.write(file.getvalue())
tmp_file_path = Path(tmp_file.name)
# Create translator
if api_provider == "ChatGPT":
translator = ChatGPTTranslator(api_key.strip())
elif api_provider == "DeepSeek":
translator = DeepSeekTranslator(api_key.strip())
else:
raise ValueError(f"Unsupported provider: {api_provider}")
# Create processor based on file extension
extension = tmp_file_path.suffix.lower()
if extension == '.pdf':
processor = PDFProcessor(translator)
elif extension == '.docx':
processor = DOCXProcessor(translator)
elif extension == '.pptx':
processor = PPTXProcessor(translator)
else:
raise ValueError(f"Unsupported file format: {extension}")
# Progress tracking
progress_bar = st.progress(0)
status_text = st.empty()
def progress_callback(progress_val, desc):
progress_bar.progress(progress_val)
status_text.text(desc)
# Process document
status_text.text("Starting translation...")
output_path, summary_text = processor.process_document(
tmp_file_path,
source_lang,
target_lang,
progress_callback
)
# Generate review
status_text.text("Generating review...")
review_text = self.generate_review(summary_text, source_lang, translator)
# Clean up temp file
tmp_file_path.unlink()
progress_bar.progress(1.0)
status_text.text("ā
Translation completed!")
return output_path, review_text, summary_text
except Exception as e:
self.logger.error(f"Translation error: {str(e)}")
raise
def generate_review(self, translated_text: str, source_lang: str, translator) -> str:
"""Generate translation review"""
try:
system_prompt = f"""Generate a comprehensive translation review in {source_lang} covering:
1. Translation quality assessment
2. Coherence and consistency
3. Technical terminology accuracy
4. Overall readability
5. Recommendations for improvement
Keep the review concise but informative."""
# Use translator's API to generate review
review = translator._make_translation_request(
f"Review this translated text:\n\n{translated_text[:2000]}...",
"English",
source_lang
)
return translator._clean_translation_output(review)
except Exception as e:
return f"Review generation failed: {str(e)}"
def render_results(self):
"""Render translation results"""
if st.session_state.translation_result:
st.markdown("## š„ Results")
col1, col2 = st.columns(2)
with col1:
st.markdown("### š Translated Document")
if st.session_state.translation_result:
with open(st.session_state.translation_result, 'rb') as file:
st.download_button(
label="ā¬ļø Download Translated Document",
data=file.read(),
file_name=Path(st.session_state.translation_result).name,
mime="application/octet-stream"
)
with col2:
st.markdown("### š Translation Review")
if st.session_state.review_result:
st.download_button(
label="ā¬ļø Download Review",
data=st.session_state.review_result,
file_name="translation_review.txt",
mime="text/plain"
)
# Summary
if hasattr(st.session_state, 'summary_text') and st.session_state.summary_text:
st.markdown("### š Translation Summary")
with st.expander("View Summary", expanded=False):
st.text_area(
"Summary",
value=st.session_state.summary_text[:1000] + "..." if len(st.session_state.summary_text) > 1000 else st.session_state.summary_text,
height=200,
disabled=True,
label_visibility="collapsed"
)
def run(self):
"""Main application loop"""
self.setup_page_config()
self.render_header()
# Sidebar configuration
api_provider, api_key, source_lang, target_lang = self.render_sidebar()
# Main content
uploaded_file = self.render_file_upload()
# Translation button
st.markdown("---")
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
translate_button = st.button(
"š Translate Document",
disabled=st.session_state.processing,
use_container_width=True
)
# Process translation
if translate_button:
# Validate inputs
errors = self.validate_inputs(uploaded_file, api_provider, api_key, source_lang, target_lang)
if errors:
st.error("ā **Please fix the following errors:**")
for error in errors:
st.error(f"⢠{error}")
else:
st.session_state.processing = True
try:
with st.spinner("Translating document..."):
output_path, review_text, summary_text = self.process_document(
uploaded_file, api_provider, api_key, source_lang, target_lang
)
# Store results
st.session_state.translation_result = output_path
st.session_state.review_result = review_text
st.session_state.summary_text = summary_text
st.success(f"ā
**Translation completed successfully!**\n\n"
f"š **File:** {uploaded_file.name}\n"
f"š **Translation:** {source_lang} ā {target_lang}\n"
f"š¤ **Provider:** {api_provider}")
# Auto-refresh to show results
st.rerun()
except Exception as e:
st.error(f"ā **Translation failed:** {str(e)}")
finally:
st.session_state.processing = False
# Show results if available
self.render_results()
# Footer
st.markdown("---")
st.markdown(
"