Py.Cafe

Scion24/

streamlit-app-pycafe

Streamlit App for Py.cafe

DocsPricing
  • handlers/
  • app.py
  • requirements.txt
  • translation_prompts.py
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
"""Main application entry point."""
import streamlit as st
import os
import logging
from openai import AzureOpenAI
from dotenv import load_dotenv
from translation_prompts import TECHNICAL_GLOSSARY, LANGUAGE_CODES
from handlers.format_router import FormatRouter, MIME_TYPES
from handlers.cache_manager import CacheManager
from handlers.translation import validate_azure_config
from handlers.translation_errors import (
    TranslationError,
    AzureDeploymentError,
    ModelNotFoundError,
    APIKeyError,
    EndpointError,
    FormatError,
    ContentTooLongError
)

# Set page config
st.set_page_config(page_title='Kotodama (θ¨€ιœŠ) 🌐', page_icon='./images/tm-logo.png', layout='wide')

# Load environment variables
load_dotenv()

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

try:
    # Validate Azure OpenAI configuration
    validate_azure_config()
    
    # Initialize Azure OpenAI client
    client = AzureOpenAI(
        api_key=os.getenv("AZURE_OPENAI_API_KEY"),
        api_version=os.getenv("AZURE_OPENAI_API_VERSION", "2024-02-15-preview"),
        azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")
    )
except TranslationError as e:
    st.error(str(e))
    st.stop()

# Setup directories
TRANSLATED_FILES_DIR = os.path.join(os.getcwd(), 'translated_files')
CACHE_DIR = os.path.join(os.getcwd(), 'cache')
os.makedirs(TRANSLATED_FILES_DIR, exist_ok=True)
os.makedirs(CACHE_DIR, exist_ok=True)

def main():
    st.image("./images/trend-logo.png", width=400)
    st.title("Kotodama (θ¨€ιœŠ) 🌐")
    st.subheader("AI-powered translation tool for technical content")
    st.divider()
    st.write(f"Upload your file for translation ({', '.join(MIME_TYPES.keys())})")
    
    # Initialize session state
    if 'translation_stopped' not in st.session_state:
        st.session_state.translation_stopped = False
    if 'translation_in_progress' not in st.session_state:
        st.session_state.translation_in_progress = False
    if 'cache_manager' not in st.session_state:
        st.session_state.cache_manager = CacheManager(CACHE_DIR)
    
    # Cache status and clear button in sidebar
    with st.sidebar:
        st.subheader("Cache Status")
        stats = st.session_state.cache_manager.get_stats()
        st.write(f"Hits: {stats['hits']}")
        st.write(f"Misses: {stats['misses']}")
        st.write(f"Total entries: {stats['total_entries']}")
        
        if st.button("Clear Cache"):
            st.session_state.cache_manager.clear()
            st.success("Cache cleared! 🧹")
            st.rerun()
    
    # File uploader with supported extensions
    uploaded_file = st.file_uploader("Choose a file", type=list(MIME_TYPES.keys()))
    
    # Language selector
    available_languages = sorted(list(LANGUAGE_CODES.keys()))
    default_idx = available_languages.index("Japanese")
    language = st.selectbox(
        "Select target language",
        available_languages,
        index=default_idx
    )
    
    # Button container
    button_container = st.container()

    # Translate button
    translate_disabled = st.session_state.translation_in_progress
    translate_button = button_container.button(
        "Translate",
        type="primary",
        disabled=translate_disabled,
        key="translate_button"
    )

    if translate_button:
        if uploaded_file is None:
            st.error("Please upload a file first!")
            st.stop()
        
        file_extension = uploaded_file.name.split('.')[-1].lower()
        st.session_state.translation_stopped = False
        st.session_state.translation_in_progress = True
        
        # Create status container for progress
        with st.status("Translating...", expanded=True) as status:
            try:
                # Initialize format router with OpenAI client
                router = FormatRouter(
                    st.session_state.cache_manager,
                    TRANSLATED_FILES_DIR,
                    client
                )
                
                # Track initial cache stats
                initial_stats = st.session_state.cache_manager.get_stats()
                
                # Create progress elements and stop button in columns
                col1, col2 = status.columns([3, 1])
                with col1:
                    progress_text = st.text("Initializing...")
                    progress_bar = st.progress(0.0)
                with col2:
                    if st.button("Stop", type="secondary", key="stop_button"):
                        st.session_state.translation_stopped = True
                        st.warning("Translation stopped by user")
                        st.session_state.translation_in_progress = False
                        st.rerun()
                
                # Create a progress callback that updates both bar and text
                def update_progress(value: float) -> None:
                    if st.session_state.translation_stopped:
                        status.update(label="Translation stopped", state="error")
                        st.stop()
                    # Ensure value is between 0 and 1
                    value = max(0.0, min(1.0, float(value)))
                    progress_bar.progress(value)
                    percent = int(value * 100)
                    progress_text.text(f"Translating... {percent}%")
                
                # Translate file
                translated_content, mime_type = router.translate_file(
                    uploaded_file,
                    file_extension,
                    language,
                    progress_callback=update_progress 
                )
                
                # Update cache stats
                final_stats = st.session_state.cache_manager.get_stats()
                new_hits = final_stats['hits'] - initial_stats['hits']
                new_misses = final_stats['misses'] - initial_stats['misses']
                
                # Update status with results
                status.update(label="Translation completed! πŸŽ‰", state="complete")
                
                if new_hits > 0:
                    st.info(f"✨ Used {new_hits} cached translations!")
                if new_misses > 0:
                    st.info(f"πŸ†• Generated {new_misses} new translations!")
                
                # Save translated file
                translated_file_path = os.path.join(TRANSLATED_FILES_DIR, f'translated.{file_extension}')
                
                # Handle binary vs text content
                if isinstance(translated_content, bytes):
                    write_mode = 'wb'
                    file_content = translated_content
                else:
                    write_mode = 'w'
                    file_content = str(translated_content)
                    if not isinstance(file_content, bytes):
                        file_content = file_content.encode('utf-8').decode('utf-8')
                    
                with open(translated_file_path, write_mode, encoding='utf-8' if write_mode == 'w' else None) as f:
                    f.write(file_content)
                
                # Center the download button
                col1, col2, col3 = st.columns([1, 2, 1])
                with col2:
                    # Ensure content is properly encoded for download
                    if isinstance(file_content, str):
                        download_content = file_content.encode('utf-8')
                    else:
                        download_content = file_content
                        
                    st.download_button(
                        label=f"Download translated {file_extension.upper()}",
                        data=download_content,
                        file_name=f"translated.{file_extension}",
                        mime=mime_type,
                        use_container_width=True
                    )
                
                st.session_state.translation_in_progress = False
            
            except AzureDeploymentError as e:
                st.session_state.translation_in_progress = False
                status.update(label="Azure OpenAI Service Error", state="error")
                st.error(str(e))
                st.info("Please check your Azure OpenAI deployment and model configuration.")
                
            except ModelNotFoundError as e:
                st.session_state.translation_in_progress = False
                status.update(label="Model Configuration Error", state="error")
                st.error(str(e))
                st.info("Make sure the model is deployed in your Azure OpenAI resource.")
                
            except APIKeyError as e:
                st.session_state.translation_in_progress = False
                status.update(label="Authentication Error", state="error")
                st.error(str(e))
                st.info("Check your .env file and Azure OpenAI API key.")
                
            except EndpointError as e:
                st.session_state.translation_in_progress = False
                status.update(label="Configuration Error", state="error")
                st.error(str(e))
                st.info("Check your .env file and Azure OpenAI endpoint URL.")
                
            except FormatError as e:
                st.session_state.translation_in_progress = False
                status.update(label="File Format Error", state="error")
                st.error(str(e))
                st.info("Please check your file format and try again.")
                
            except ContentTooLongError as e:
                st.session_state.translation_in_progress = False
                status.update(label="Content Length Error", state="error")
                st.error(str(e))
                st.info("Try splitting your file into smaller parts.")
                
            except Exception as e:
                st.session_state.translation_in_progress = False
                status.update(label="An unexpected error occurred", state="error")
                st.error(str(e))
                logging.error(f"Translation error: {str(e)}")
                st.info("Please try again or contact support if the issue persists.")
    st.divider()
    st.write("Made with ❀️ by [Fernando Tucci](mailto:fernando_tucci@trendmicro.com)")
if __name__ == '__main__':
    main()