fisherman611 commited on
Commit
cc999f3
Β·
verified Β·
1 Parent(s): f6f1729

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +817 -567
app.py CHANGED
@@ -1,567 +1,817 @@
1
- import logging
2
- from typing import Dict, Any, Tuple
3
- import torch
4
- import gradio as gr
5
- from infer import ModelLoader, DEVICE, Translator
6
- from models.statistical_mt import LanguageModel
7
-
8
- # Configure logging
9
- logging.basicConfig(
10
- level=logging.INFO,
11
- format="%(asctime)s - %(levelname)s - %(message)s",
12
- handlers=[logging.StreamHandler()]
13
- )
14
- logger = logging.getLogger(__name__)
15
-
16
- # Store models and tokenizers
17
- MODELS: Dict[str, Tuple[Any, Any]] = {
18
- "mbart50": (None, None),
19
- "mt5": (None, None),
20
- "rbmt": (None, None),
21
- "smt": (None, None)
22
- }
23
-
24
- def initialize_models(model_types: list[str] = ["mbart50", "mt5", "rbmt", "smt"]) -> None:
25
- """Initialize translation models and store them in MODELS dictionary.
26
-
27
- Args:
28
- model_types: List of model types to initialize.
29
- """
30
- global MODELS
31
- for model_type in model_types:
32
- try:
33
- if model_type == "mbart50":
34
- logger.info("Loading MBart50 model...")
35
- MODELS["mbart50"] = ModelLoader.load_mbart50()
36
- logger.info(f"MBart50 model loaded on {DEVICE}")
37
- elif model_type == "mt5":
38
- logger.info("Loading MT5 model...")
39
- MODELS["mt5"] = ModelLoader.load_mt5()
40
- logger.info(f"MT5 model loaded on {DEVICE}")
41
- elif model_type == "rbmt":
42
- logger.info("Initializing RBMT...")
43
- from models.rule_based_mt import TransferBasedMT
44
- MODELS["rbmt"] = (TransferBasedMT(), None)
45
- logger.info("RBMT initialized")
46
- elif model_type == "smt":
47
- logger.info("Initializing SMT...")
48
- MODELS["smt"] = (ModelLoader.load_smt(), None)
49
- logger.info("SMT initialized")
50
- except Exception as e:
51
- logger.error(f"Failed to initialize {model_type}: {str(e)}")
52
- MODELS[model_type] = (None, None)
53
-
54
- def translate_text(model_type: str, input_text: str) -> str:
55
- """Translate input text using the selected model.
56
-
57
- Args:
58
- model_type: Type of model to use ('rbmt', 'smt', 'mbart50', 'mt5').
59
- input_text: English text to translate.
60
-
61
- Returns:
62
- Translated text or error message.
63
- """
64
- try:
65
- model, tokenizer = MODELS.get(model_type, (None, None))
66
- if model is None:
67
- return f"Error: Model '{model_type}' not loaded or not supported."
68
- if model_type == "rbmt":
69
- return Translator.translate_rbmt(input_text)
70
- elif model_type == "smt":
71
- return Translator.translate_smt(input_text, model)
72
- elif model_type == "mbart50":
73
- return Translator.translate_mbart50(input_text, model, tokenizer)
74
- else: # mt5
75
- return Translator.translate_mt5(input_text, model, tokenizer)
76
- except Exception as e:
77
- return f"Error during translation: {str(e)}"
78
-
79
- # Initialize models before launching the app
80
- logger.info("Starting model initialization...")
81
- initialize_models()
82
- logger.info("Model initialization complete.")
83
-
84
- # Define Gradio interface
85
- with gr.Blocks(
86
- theme="soft",
87
- title="English to Vietnamese Translator",
88
- css="""
89
- /* Root variables for consistent theming */
90
- :root {
91
- --primary-color: #2563eb;
92
- --primary-hover: #1d4ed8;
93
- --secondary-color: #64748b;
94
- --success-color: #10b981;
95
- --error-color: #ef4444;
96
- --warning-color: #f59e0b;
97
- --background-primary: #ffffff;
98
- --background-secondary: #f8fafc;
99
- --background-tertiary: #f1f5f9;
100
- --text-primary: #1e293b;
101
- --text-secondary: #64748b;
102
- --border-color: #e2e8f0;
103
- --border-radius: 12px;
104
- --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
105
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
106
- --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
107
- --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
108
- }
109
-
110
- /* Global styles */
111
- * {
112
- box-sizing: border-box;
113
- }
114
-
115
- body {
116
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
117
- line-height: 1.6;
118
- color: var(--text-primary);
119
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
120
- min-height: 100vh;
121
- }
122
-
123
- /* Main container */
124
- .gradio-container {
125
- max-width: 1200px;
126
- margin: 0 auto;
127
- padding: 2rem;
128
- }
129
-
130
- /* Header styling */
131
- .header {
132
- text-align: center;
133
- margin-bottom: 3rem;
134
- padding: 2rem;
135
- background: var(--background-primary);
136
- border-radius: var(--border-radius);
137
- box-shadow: var(--shadow-lg);
138
- backdrop-filter: blur(10px);
139
- border: 1px solid rgba(255, 255, 255, 0.2);
140
- }
141
-
142
- .header h1 {
143
- font-size: 2.5rem;
144
- font-weight: 700;
145
- color: var(--primary-color);
146
- margin-bottom: 0.5rem;
147
- text-shadow: 0 2px 4px rgba(37, 99, 235, 0.2);
148
- position: relative;
149
- z-index: 1;
150
- }
151
-
152
- /* Enhanced gradient text effect for supported browsers */
153
- @supports (-webkit-background-clip: text) {
154
- .header h1 {
155
- background: linear-gradient(135deg, var(--primary-color), #7c3aed, #ec4899, var(--primary-color));
156
- background-size: 200% 200%;
157
- -webkit-background-clip: text;
158
- -webkit-text-fill-color: transparent;
159
- background-clip: text;
160
- animation: gradientShift 4s ease-in-out infinite;
161
- }
162
- }
163
-
164
- @keyframes gradientShift {
165
- 0%, 100% { background-position: 0% 50%; }
166
- 50% { background-position: 100% 50%; }
167
- }
168
-
169
- .header p {
170
- color: var(--text-secondary);
171
- font-size: 1.1rem;
172
- margin: 0;
173
- }
174
-
175
- /* Main content container */
176
- .main-container {
177
- background: var(--background-primary);
178
- border-radius: var(--border-radius);
179
- padding: 2.5rem;
180
- box-shadow: var(--shadow-lg);
181
- backdrop-filter: blur(10px);
182
- border: 1px solid rgba(255, 255, 255, 0.2);
183
- transition: var(--transition);
184
- }
185
-
186
- .main-container:hover {
187
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
188
- }
189
-
190
- /* Model selection styling */
191
- .model-section {
192
- margin-bottom: 2rem;
193
- }
194
-
195
- .model-label {
196
- font-weight: 600;
197
- color: var(--text-primary);
198
- margin-bottom: 0.5rem;
199
- display: block;
200
- }
201
-
202
- .gr-dropdown {
203
- border-radius: var(--border-radius) !important;
204
- border: 2px solid var(--border-color) !important;
205
- transition: var(--transition) !important;
206
- background: var(--background-primary) !important;
207
- }
208
-
209
- .gr-dropdown:focus-within {
210
- border-color: var(--primary-color) !important;
211
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important;
212
- }
213
-
214
- .gr-dropdown .options {
215
- background: var(--background-primary) !important;
216
- border: 1px solid var(--border-color) !important;
217
- border-radius: var(--border-radius) !important;
218
- box-shadow: var(--shadow-lg) !important;
219
- }
220
-
221
- .gr-dropdown .options .item {
222
- padding: 0.75rem 1rem !important;
223
- transition: var(--transition) !important;
224
- border-radius: 8px !important;
225
- margin: 0.25rem !important;
226
- }
227
-
228
- .gr-dropdown .options .item:hover {
229
- background-color: var(--background-secondary) !important;
230
- cursor: pointer;
231
- transform: translateY(-1px);
232
- }
233
-
234
- .gr-dropdown .options .item.selected {
235
- background-color: var(--primary-color) !important;
236
- color: white !important;
237
- }
238
-
239
- /* Input/Output sections */
240
- .io-section {
241
- display: grid;
242
- grid-template-columns: 1fr 1fr;
243
- gap: 2rem;
244
- margin-bottom: 2rem;
245
- }
246
-
247
- @media (max-width: 768px) {
248
- .io-section {
249
- grid-template-columns: 1fr;
250
- gap: 1.5rem;
251
- }
252
- }
253
-
254
- .input-section, .output-section {
255
- background: var(--background-secondary);
256
- padding: 1.5rem;
257
- border-radius: var(--border-radius);
258
- border: 1px solid var(--border-color);
259
- transition: var(--transition);
260
- }
261
-
262
- .input-section:hover, .output-section:hover {
263
- border-color: var(--primary-color);
264
- box-shadow: var(--shadow-md);
265
- }
266
-
267
- .section-title {
268
- font-weight: 600;
269
- color: var(--text-primary);
270
- margin-bottom: 1rem;
271
- display: flex;
272
- align-items: center;
273
- gap: 0.5rem;
274
- }
275
-
276
- .section-title::before {
277
- content: "";
278
- width: 4px;
279
- height: 20px;
280
- background: var(--primary-color);
281
- border-radius: 2px;
282
- }
283
-
284
- /* Textbox styling */
285
- .gr-textbox {
286
- border-radius: var(--border-radius) !important;
287
- border: 2px solid var(--border-color) !important;
288
- transition: var(--transition) !important;
289
- background: var(--background-primary) !important;
290
- font-size: 1rem !important;
291
- line-height: 1.5 !important;
292
- }
293
-
294
- .gr-textbox:focus {
295
- border-color: var(--primary-color) !important;
296
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important;
297
- outline: none !important;
298
- }
299
-
300
- .gr-textbox textarea {
301
- resize: vertical !important;
302
- min-height: 120px !important;
303
- }
304
-
305
- /* Button styling */
306
- .translate-button {
307
- background: linear-gradient(135deg, var(--primary-color), #7c3aed) !important;
308
- color: white !important;
309
- border: none !important;
310
- border-radius: var(--border-radius) !important;
311
- padding: 1rem 2rem !important;
312
- font-size: 1.1rem !important;
313
- font-weight: 600 !important;
314
- cursor: pointer !important;
315
- transition: var(--transition) !important;
316
- box-shadow: var(--shadow-md) !important;
317
- text-transform: uppercase !important;
318
- letter-spacing: 0.5px !important;
319
- position: relative !important;
320
- overflow: hidden !important;
321
- }
322
-
323
- .translate-button:hover {
324
- transform: translateY(-2px) !important;
325
- box-shadow: var(--shadow-lg) !important;
326
- }
327
-
328
- .translate-button:active {
329
- transform: translateY(0) !important;
330
- }
331
-
332
- .translate-button::before {
333
- content: "";
334
- position: absolute;
335
- top: 0;
336
- left: -100%;
337
- width: 100%;
338
- height: 100%;
339
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
340
- transition: left 0.5s;
341
- }
342
-
343
- .translate-button:hover::before {
344
- left: 100%;
345
- }
346
-
347
- /* Loading animation */
348
- .loading {
349
- display: inline-block;
350
- width: 20px;
351
- height: 20px;
352
- border: 3px solid rgba(255, 255, 255, 0.3);
353
- border-radius: 50%;
354
- border-top-color: white;
355
- animation: spin 1s ease-in-out infinite;
356
- margin-right: 0.5rem;
357
- }
358
-
359
- @keyframes spin {
360
- to { transform: rotate(360deg); }
361
- }
362
-
363
- /* Progress bar styling */
364
- .progress-bar {
365
- background: var(--primary-color) !important;
366
- border-radius: 4px !important;
367
- height: 4px !important;
368
- }
369
-
370
- /* Model info cards */
371
- .model-info {
372
- display: grid;
373
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
374
- gap: 1rem;
375
- margin-top: 2rem;
376
- padding-top: 2rem;
377
- border-top: 1px solid var(--border-color);
378
- }
379
-
380
- .model-card {
381
- background: var(--background-secondary);
382
- padding: 1rem;
383
- border-radius: var(--border-radius);
384
- border: 1px solid var(--border-color);
385
- transition: var(--transition);
386
- text-align: center;
387
- }
388
-
389
- .model-card:hover {
390
- border-color: var(--primary-color);
391
- transform: translateY(-2px);
392
- box-shadow: var(--shadow-md);
393
- }
394
-
395
- .model-card h3 {
396
- color: var(--primary-color);
397
- margin-bottom: 0.5rem;
398
- font-size: 1.1rem;
399
- }
400
-
401
- .model-card p {
402
- color: var(--text-secondary);
403
- font-size: 0.9rem;
404
- margin: 0;
405
- }
406
-
407
- /* Responsive design */
408
- @media (max-width: 1024px) {
409
- .gradio-container {
410
- padding: 1rem;
411
- }
412
-
413
- .main-container {
414
- padding: 1.5rem;
415
- }
416
-
417
- .header h1 {
418
- font-size: 2rem;
419
- }
420
- }
421
-
422
- @media (max-width: 640px) {
423
- .header {
424
- padding: 1.5rem;
425
- margin-bottom: 2rem;
426
- }
427
-
428
- .header h1 {
429
- font-size: 1.8rem;
430
- }
431
-
432
- .main-container {
433
- padding: 1rem;
434
- }
435
-
436
- .translate-button {
437
- width: 100% !important;
438
- padding: 0.875rem 1.5rem !important;
439
- }
440
- }
441
-
442
- /* Accessibility improvements */
443
- .sr-only {
444
- position: absolute;
445
- width: 1px;
446
- height: 1px;
447
- padding: 0;
448
- margin: -1px;
449
- overflow: hidden;
450
- clip: rect(0, 0, 0, 0);
451
- white-space: nowrap;
452
- border: 0;
453
- }
454
-
455
- /* Focus styles for accessibility */
456
- *:focus {
457
- outline: 2px solid var(--primary-color);
458
- outline-offset: 2px;
459
- }
460
-
461
- /* Custom scrollbar */
462
- ::-webkit-scrollbar {
463
- width: 8px;
464
- }
465
-
466
- ::-webkit-scrollbar-track {
467
- background: var(--background-secondary);
468
- }
469
-
470
- ::-webkit-scrollbar-thumb {
471
- background: var(--primary-color);
472
- border-radius: 4px;
473
- }
474
-
475
- ::-webkit-scrollbar-thumb:hover {
476
- background: var(--primary-hover);
477
- }
478
- """
479
- ) as demo:
480
- # Header section
481
- with gr.Column(elem_classes=["header"]):
482
- gr.HTML("""
483
- <h1>🌐 English to Vietnamese Machine Translation</h1>
484
- <p>Advanced AI-powered translation with multiple model options</p>
485
- """)
486
-
487
- # Main content
488
- with gr.Column(elem_classes=["main-container"]):
489
- # Model selection
490
- with gr.Row(elem_classes=["model-section"]):
491
- model_choice = gr.Dropdown(
492
- choices=[
493
- ("Rule-Based MT (RBMT)", "rbmt"),
494
- ("Statistical MT (SMT)", "smt"),
495
- ("MBart50 (Neural)", "mbart50"),
496
- ("mT5 (Neural)", "mt5")
497
- ],
498
- label="πŸ€– Select Translation Model",
499
- value="mbart50",
500
- elem_classes=["gr-dropdown"],
501
- info="Choose the translation approach that best fits your needs"
502
- )
503
-
504
- # Input/Output section
505
- with gr.Row(elem_classes=["io-section"]):
506
- with gr.Column(elem_classes=["input-section"]):
507
- gr.HTML('<div class="section-title">πŸ“ Input Text (English)</div>')
508
- input_text = gr.Textbox(
509
- placeholder="Enter your English text here...\n\nExample: Hello, how are you today?",
510
- lines=6,
511
- elem_classes=["gr-textbox"],
512
- show_label=False,
513
- container=False
514
- )
515
-
516
- with gr.Column(elem_classes=["output-section"]):
517
- gr.HTML('<div class="section-title">πŸ‡»πŸ‡³ Translation (Vietnamese)</div>')
518
- output_text = gr.Textbox(
519
- placeholder="Translation will appear here...",
520
- lines=6,
521
- elem_classes=["gr-textbox"],
522
- interactive=False,
523
- show_label=False,
524
- container=False
525
- )
526
-
527
- # Translate button
528
- translate_button = gr.Button(
529
- "πŸš€ Translate Text",
530
- elem_classes=["translate-button"],
531
- variant="primary",
532
- size="lg"
533
- )
534
-
535
- # Model information cards
536
- gr.HTML("""
537
- <div class="model-info">
538
- <div class="model-card">
539
- <h3>RBMT</h3>
540
- <p>Rule-based approach using linguistic rules and dictionaries</p>
541
- </div>
542
- <div class="model-card">
543
- <h3>SMT</h3>
544
- <p>Statistical model trained on parallel corpora</p>
545
- </div>
546
- <div class="model-card">
547
- <h3>MBart50</h3>
548
- <p>Facebook's multilingual BART model</p>
549
- </div>
550
- <div class="model-card">
551
- <h3>mT5</h3>
552
- <p>Google's multilingual T5 transformer</p>
553
- </div>
554
- </div>
555
- """)
556
-
557
- # Bind the translation function to the button
558
- translate_button.click(
559
- fn=translate_text,
560
- inputs=[model_choice, input_text],
561
- outputs=output_text,
562
- show_progress=True
563
- )
564
-
565
- # Launch the app
566
- if __name__ == "__main__":
567
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ English to Vietnamese Machine Translation Web Application
3
+
4
+ This module provides a Gradio-based web interface for translating English text
5
+ to Vietnamese using multiple translation approaches including rule-based,
6
+ statistical, and neural machine translation models.
7
+ """
8
+
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Dict, Any, Tuple, Optional
12
+ import torch
13
+ import gradio as gr
14
+
15
+ from infer import ModelLoader, DEVICE, Translator
16
+ from models.statistical_mt import LanguageModel
17
+
18
+ # =============================================================================
19
+ # CONFIGURATION & CONSTANTS
20
+ # =============================================================================
21
+
22
+ # Application metadata
23
+ APP_TITLE = "English to Vietnamese Machine Translation"
24
+ APP_DESCRIPTION = "Advanced AI-powered translation with multiple model options"
25
+ MAX_INPUT_LENGTH = 1000
26
+ DEFAULT_MODEL = "mbart50"
27
+
28
+ # Example questions for users to try
29
+ EXAMPLE_QUESTIONS = [
30
+ "Hello, how are you today?",
31
+ "What time is it now?",
32
+ "I would like to order some food.",
33
+ "Where is the nearest hospital?",
34
+ "Can you help me with directions to the airport?",
35
+ "The weather is beautiful today.",
36
+ "I am learning Vietnamese language.",
37
+ "Thank you for your help.",
38
+ "How much does this cost?",
39
+ "I need to book a hotel room.",
40
+ "What is your favorite Vietnamese dish?",
41
+ "I love traveling to Vietnam.",
42
+ "Could you please speak more slowly?",
43
+ "I don't understand what you're saying.",
44
+ "Have a wonderful day!"
45
+ ]
46
+
47
+ # Model configuration
48
+ MODEL_CHOICES = [
49
+ ("Rule-Based MT (RBMT)", "rbmt"),
50
+ ("Statistical MT (SMT)", "smt"),
51
+ ("MBart50 (Neural)", "mbart50"),
52
+ ("mT5 (Neural)", "mt5")
53
+ ]
54
+
55
+ MODEL_DESCRIPTIONS = {
56
+ "rbmt": "Rule-based approach using linguistic rules and dictionaries",
57
+ "smt": "Statistical model trained on parallel corpora",
58
+ "mbart50": "Facebook's multilingual BART model",
59
+ "mt5": "Google's multilingual T5 transformer"
60
+ }
61
+
62
+ # =============================================================================
63
+ # LOGGING CONFIGURATION
64
+ # =============================================================================
65
+
66
+ def setup_logging() -> logging.Logger:
67
+ """Configure and return logger instance."""
68
+ logging.basicConfig(
69
+ level=logging.INFO,
70
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
71
+ handlers=[logging.StreamHandler()]
72
+ )
73
+ return logging.getLogger(__name__)
74
+
75
+ logger = setup_logging()
76
+
77
+ # =============================================================================
78
+ # MODEL MANAGEMENT
79
+ # =============================================================================
80
+
81
+ class ModelManager:
82
+ """Manages loading and storage of translation models."""
83
+
84
+ def __init__(self):
85
+ """Initialize model storage."""
86
+ self.models: Dict[str, Tuple[Any, Any]] = {
87
+ "mbart50": (None, None),
88
+ "mt5": (None, None),
89
+ "rbmt": (None, None),
90
+ "smt": (None, None)
91
+ }
92
+
93
+ def initialize_models(self, model_types: list[str] = None) -> None:
94
+ """Initialize specified translation models.
95
+
96
+ Args:
97
+ model_types: List of model types to initialize.
98
+ Defaults to all available models.
99
+ """
100
+ if model_types is None:
101
+ model_types = ["mbart50", "mt5", "rbmt", "smt"]
102
+
103
+ logger.info("Starting model initialization...")
104
+
105
+ for model_type in model_types:
106
+ try:
107
+ self._load_single_model(model_type)
108
+ except Exception as e:
109
+ logger.error(f"Failed to initialize {model_type}: {str(e)}")
110
+ self.models[model_type] = (None, None)
111
+
112
+ logger.info("Model initialization complete.")
113
+
114
+ def _load_single_model(self, model_type: str) -> None:
115
+ """Load a single model based on its type.
116
+
117
+ Args:
118
+ model_type: Type of model to load.
119
+ """
120
+ logger.info(f"Loading {model_type.upper()} model...")
121
+
122
+ if model_type == "mbart50":
123
+ self.models["mbart50"] = ModelLoader.load_mbart50()
124
+ logger.info(f"MBart50 model loaded on {DEVICE}")
125
+
126
+ elif model_type == "mt5":
127
+ self.models["mt5"] = ModelLoader.load_mt5()
128
+ logger.info(f"MT5 model loaded on {DEVICE}")
129
+
130
+ elif model_type == "rbmt":
131
+ from models.rule_based_mt import TransferBasedMT
132
+ self.models["rbmt"] = (TransferBasedMT(), None)
133
+ logger.info("RBMT initialized")
134
+
135
+ elif model_type == "smt":
136
+ self.models["smt"] = (ModelLoader.load_smt(), None)
137
+ logger.info("SMT initialized")
138
+
139
+ else:
140
+ raise ValueError(f"Unknown model type: {model_type}")
141
+
142
+ def get_model(self, model_type: str) -> Tuple[Any, Any]:
143
+ """Get model and tokenizer for specified type.
144
+
145
+ Args:
146
+ model_type: Type of model to retrieve.
147
+
148
+ Returns:
149
+ Tuple of (model, tokenizer).
150
+ """
151
+ return self.models.get(model_type, (None, None))
152
+
153
+ def is_model_loaded(self, model_type: str) -> bool:
154
+ """Check if a model is loaded and ready.
155
+
156
+ Args:
157
+ model_type: Type of model to check.
158
+
159
+ Returns:
160
+ True if model is loaded, False otherwise.
161
+ """
162
+ model, _ = self.get_model(model_type)
163
+ return model is not None
164
+
165
+ # =============================================================================
166
+ # TRANSLATION SERVICE
167
+ # =============================================================================
168
+
169
+ class TranslationService:
170
+ """Handles translation requests using different models."""
171
+
172
+ def __init__(self, model_manager: ModelManager):
173
+ """Initialize translation service.
174
+
175
+ Args:
176
+ model_manager: Instance of ModelManager for accessing models.
177
+ """
178
+ self.model_manager = model_manager
179
+
180
+ def translate(self, model_type: str, input_text: str) -> str:
181
+ """Translate input text using the selected model.
182
+
183
+ Args:
184
+ model_type: Type of model to use for translation.
185
+ input_text: English text to translate.
186
+
187
+ Returns:
188
+ Translated Vietnamese text or error message.
189
+ """
190
+ # Input validation
191
+ if not input_text or not input_text.strip():
192
+ return "Please enter some text to translate."
193
+
194
+ if len(input_text) > MAX_INPUT_LENGTH:
195
+ return f"Input text too long. Maximum {MAX_INPUT_LENGTH} characters allowed."
196
+
197
+ # Check if model is loaded
198
+ if not self.model_manager.is_model_loaded(model_type):
199
+ return f"Error: Model '{model_type}' is not loaded or not supported."
200
+
201
+ try:
202
+ return self._perform_translation(model_type, input_text.strip())
203
+ except Exception as e:
204
+ logger.error(f"Translation error with {model_type}: {str(e)}")
205
+ return f"Translation failed: {str(e)}"
206
+
207
+ def _perform_translation(self, model_type: str, text: str) -> str:
208
+ """Perform the actual translation based on model type.
209
+
210
+ Args:
211
+ model_type: Type of model to use.
212
+ text: Text to translate.
213
+
214
+ Returns:
215
+ Translated text.
216
+ """
217
+ model, tokenizer = self.model_manager.get_model(model_type)
218
+
219
+ if model_type == "rbmt":
220
+ return Translator.translate_rbmt(text)
221
+ elif model_type == "smt":
222
+ return Translator.translate_smt(text, model)
223
+ elif model_type == "mbart50":
224
+ return Translator.translate_mbart50(text, model, tokenizer)
225
+ elif model_type == "mt5":
226
+ return Translator.translate_mt5(text, model, tokenizer)
227
+ else:
228
+ raise ValueError(f"Unknown model type: {model_type}")
229
+
230
+ # =============================================================================
231
+ # UI STYLING
232
+ # =============================================================================
233
+
234
+ def get_custom_css() -> str:
235
+ """Return custom CSS styles for the application."""
236
+ return """
237
+ /* Root variables for consistent theming */
238
+ :root {
239
+ --primary-color: #2563eb;
240
+ --primary-hover: #1d4ed8;
241
+ --secondary-color: #64748b;
242
+ --success-color: #10b981;
243
+ --error-color: #ef4444;
244
+ --warning-color: #f59e0b;
245
+ --background-primary: #ffffff;
246
+ --background-secondary: #f8fafc;
247
+ --background-tertiary: #f1f5f9;
248
+ --text-primary: #1e293b;
249
+ --text-secondary: #64748b;
250
+ --border-color: #e2e8f0;
251
+ --border-radius: 12px;
252
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
253
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
254
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
255
+ --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
256
+ }
257
+
258
+ /* Global styles */
259
+ * {
260
+ box-sizing: border-box;
261
+ }
262
+
263
+ body {
264
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
265
+ line-height: 1.6;
266
+ color: var(--text-primary);
267
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
268
+ min-height: 100vh;
269
+ }
270
+
271
+ /* Main container */
272
+ .gradio-container {
273
+ max-width: 1200px;
274
+ margin: 0 auto;
275
+ padding: 2rem;
276
+ }
277
+
278
+ /* Header styling */
279
+ .header {
280
+ text-align: center;
281
+ margin-bottom: 3rem;
282
+ padding: 2rem;
283
+ background: var(--background-primary);
284
+ border-radius: var(--border-radius);
285
+ box-shadow: var(--shadow-lg);
286
+ backdrop-filter: blur(10px);
287
+ border: 1px solid rgba(255, 255, 255, 0.2);
288
+ }
289
+
290
+ .header h1 {
291
+ font-size: 2.5rem;
292
+ font-weight: 700;
293
+ color: var(--primary-color);
294
+ margin-bottom: 0.5rem;
295
+ text-shadow: 0 2px 4px rgba(37, 99, 235, 0.2);
296
+ position: relative;
297
+ z-index: 1;
298
+ }
299
+
300
+ /* Enhanced gradient text effect for supported browsers */
301
+ @supports (-webkit-background-clip: text) {
302
+ .header h1 {
303
+ background: linear-gradient(135deg, var(--primary-color), #7c3aed, #ec4899, var(--primary-color));
304
+ background-size: 200% 200%;
305
+ -webkit-background-clip: text;
306
+ -webkit-text-fill-color: transparent;
307
+ background-clip: text;
308
+ animation: gradientShift 4s ease-in-out infinite;
309
+ }
310
+ }
311
+
312
+ @keyframes gradientShift {
313
+ 0%, 100% { background-position: 0% 50%; }
314
+ 50% { background-position: 100% 50%; }
315
+ }
316
+
317
+ .header p {
318
+ color: var(--text-secondary);
319
+ font-size: 1.1rem;
320
+ margin: 0;
321
+ }
322
+
323
+ /* Main content container */
324
+ .main-container {
325
+ background: var(--background-primary);
326
+ border-radius: var(--border-radius);
327
+ padding: 2.5rem;
328
+ box-shadow: var(--shadow-lg);
329
+ backdrop-filter: blur(10px);
330
+ border: 1px solid rgba(255, 255, 255, 0.2);
331
+ transition: var(--transition);
332
+ }
333
+
334
+ .main-container:hover {
335
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
336
+ }
337
+
338
+ /* Model selection styling */
339
+ .model-section {
340
+ margin-bottom: 2rem;
341
+ }
342
+
343
+ .gr-dropdown {
344
+ border-radius: var(--border-radius) !important;
345
+ border: 2px solid var(--border-color) !important;
346
+ transition: var(--transition) !important;
347
+ background: var(--background-primary) !important;
348
+ }
349
+
350
+ .gr-dropdown:focus-within {
351
+ border-color: var(--primary-color) !important;
352
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important;
353
+ }
354
+
355
+ /* Input/Output sections */
356
+ .io-section {
357
+ display: grid;
358
+ grid-template-columns: 1fr 1fr;
359
+ gap: 2rem;
360
+ margin-bottom: 2rem;
361
+ }
362
+
363
+ @media (max-width: 768px) {
364
+ .io-section {
365
+ grid-template-columns: 1fr;
366
+ gap: 1.5rem;
367
+ }
368
+ }
369
+
370
+ .input-section, .output-section {
371
+ background: var(--background-secondary);
372
+ padding: 1.5rem;
373
+ border-radius: var(--border-radius);
374
+ border: 1px solid var(--border-color);
375
+ transition: var(--transition);
376
+ }
377
+
378
+ .input-section:hover, .output-section:hover {
379
+ border-color: var(--primary-color);
380
+ box-shadow: var(--shadow-md);
381
+ }
382
+
383
+ .section-title {
384
+ font-weight: 600;
385
+ color: var(--text-primary);
386
+ margin-bottom: 1rem;
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 0.5rem;
390
+ }
391
+
392
+ .section-title::before {
393
+ content: "";
394
+ width: 4px;
395
+ height: 20px;
396
+ background: var(--primary-color);
397
+ border-radius: 2px;
398
+ }
399
+
400
+ /* Textbox styling */
401
+ .gr-textbox {
402
+ border-radius: var(--border-radius) !important;
403
+ border: 2px solid var(--border-color) !important;
404
+ transition: var(--transition) !important;
405
+ background: var(--background-primary) !important;
406
+ font-size: 1rem !important;
407
+ line-height: 1.5 !important;
408
+ }
409
+
410
+ .gr-textbox:focus {
411
+ border-color: var(--primary-color) !important;
412
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important;
413
+ outline: none !important;
414
+ }
415
+
416
+ .gr-textbox textarea {
417
+ resize: vertical !important;
418
+ min-height: 120px !important;
419
+ }
420
+
421
+ /* Button styling */
422
+ .translate-button {
423
+ background: linear-gradient(135deg, var(--primary-color), #7c3aed) !important;
424
+ color: white !important;
425
+ border: none !important;
426
+ border-radius: var(--border-radius) !important;
427
+ padding: 1rem 2rem !important;
428
+ font-size: 1.1rem !important;
429
+ font-weight: 600 !important;
430
+ cursor: pointer !important;
431
+ transition: var(--transition) !important;
432
+ box-shadow: var(--shadow-md) !important;
433
+ text-transform: uppercase !important;
434
+ letter-spacing: 0.5px !important;
435
+ position: relative !important;
436
+ overflow: hidden !important;
437
+ }
438
+
439
+ .translate-button:hover {
440
+ transform: translateY(-2px) !important;
441
+ box-shadow: var(--shadow-lg) !important;
442
+ }
443
+
444
+ .translate-button:active {
445
+ transform: translateY(0) !important;
446
+ }
447
+
448
+ .translate-button::before {
449
+ content: "";
450
+ position: absolute;
451
+ top: 0;
452
+ left: -100%;
453
+ width: 100%;
454
+ height: 100%;
455
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
456
+ transition: left 0.5s;
457
+ }
458
+
459
+ .translate-button:hover::before {
460
+ left: 100%;
461
+ }
462
+
463
+ /* Clear button styling */
464
+ .gr-button[variant="secondary"] {
465
+ background: var(--background-secondary) !important;
466
+ color: var(--text-secondary) !important;
467
+ border: 2px solid var(--border-color) !important;
468
+ border-radius: var(--border-radius) !important;
469
+ transition: var(--transition) !important;
470
+ font-weight: 500 !important;
471
+ }
472
+
473
+ .gr-button[variant="secondary"]:hover {
474
+ background: var(--error-color) !important;
475
+ color: white !important;
476
+ border-color: var(--error-color) !important;
477
+ transform: translateY(-1px) !important;
478
+ box-shadow: var(--shadow-md) !important;
479
+ }
480
+
481
+ /* Examples section */
482
+ .examples-section {
483
+ margin: 2rem 0;
484
+ padding: 1.5rem;
485
+ background: var(--background-secondary);
486
+ border-radius: var(--border-radius);
487
+ border: 1px solid var(--border-color);
488
+ }
489
+
490
+ .examples-title {
491
+ font-weight: 600;
492
+ color: var(--text-primary);
493
+ margin-bottom: 1rem;
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 0.5rem;
497
+ font-size: 1.1rem;
498
+ }
499
+
500
+ .examples-title::before {
501
+ content: "πŸ’‘";
502
+ font-size: 1.2rem;
503
+ }
504
+
505
+ /* Style for example buttons */
506
+ .gr-examples .gr-button {
507
+ background: var(--background-primary) !important;
508
+ border: 1px solid var(--border-color) !important;
509
+ border-radius: 8px !important;
510
+ padding: 0.5rem 1rem !important;
511
+ margin: 0.25rem !important;
512
+ font-size: 0.9rem !important;
513
+ color: var(--text-primary) !important;
514
+ transition: var(--transition) !important;
515
+ text-align: left !important;
516
+ max-width: none !important;
517
+ white-space: normal !important;
518
+ word-wrap: break-word !important;
519
+ }
520
+
521
+ .gr-examples .gr-button:hover {
522
+ background: var(--primary-color) !important;
523
+ color: white !important;
524
+ border-color: var(--primary-color) !important;
525
+ transform: translateY(-1px) !important;
526
+ box-shadow: var(--shadow-sm) !important;
527
+ }
528
+
529
+ .gr-examples .gr-button:active {
530
+ transform: translateY(0) !important;
531
+ }
532
+
533
+ /* Model info cards */
534
+ .model-info {
535
+ display: grid;
536
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
537
+ gap: 1rem;
538
+ margin-top: 2rem;
539
+ padding-top: 2rem;
540
+ border-top: 1px solid var(--border-color);
541
+ }
542
+
543
+ .model-card {
544
+ background: var(--background-secondary);
545
+ padding: 1rem;
546
+ border-radius: var(--border-radius);
547
+ border: 1px solid var(--border-color);
548
+ transition: var(--transition);
549
+ text-align: center;
550
+ }
551
+
552
+ .model-card:hover {
553
+ border-color: var(--primary-color);
554
+ transform: translateY(-2px);
555
+ box-shadow: var(--shadow-md);
556
+ }
557
+
558
+ .model-card h3 {
559
+ color: var(--primary-color);
560
+ margin-bottom: 0.5rem;
561
+ font-size: 1.1rem;
562
+ }
563
+
564
+ .model-card p {
565
+ color: var(--text-secondary);
566
+ font-size: 0.9rem;
567
+ margin: 0;
568
+ }
569
+
570
+ /* Responsive design */
571
+ @media (max-width: 1024px) {
572
+ .gradio-container {
573
+ padding: 1rem;
574
+ }
575
+
576
+ .main-container {
577
+ padding: 1.5rem;
578
+ }
579
+
580
+ .header h1 {
581
+ font-size: 2rem;
582
+ }
583
+ }
584
+
585
+ @media (max-width: 640px) {
586
+ .header {
587
+ padding: 1.5rem;
588
+ margin-bottom: 2rem;
589
+ }
590
+
591
+ .header h1 {
592
+ font-size: 1.8rem;
593
+ }
594
+
595
+ .main-container {
596
+ padding: 1rem;
597
+ }
598
+
599
+ .translate-button {
600
+ width: 100% !important;
601
+ padding: 0.875rem 1.5rem !important;
602
+ }
603
+ }
604
+
605
+ /* Accessibility improvements */
606
+ .sr-only {
607
+ position: absolute;
608
+ width: 1px;
609
+ height: 1px;
610
+ padding: 0;
611
+ margin: -1px;
612
+ overflow: hidden;
613
+ clip: rect(0, 0, 0, 0);
614
+ white-space: nowrap;
615
+ border: 0;
616
+ }
617
+
618
+ /* Focus styles for accessibility */
619
+ *:focus {
620
+ outline: 2px solid var(--primary-color);
621
+ outline-offset: 2px;
622
+ }
623
+
624
+ /* Custom scrollbar */
625
+ ::-webkit-scrollbar {
626
+ width: 8px;
627
+ }
628
+
629
+ ::-webkit-scrollbar-track {
630
+ background: var(--background-secondary);
631
+ }
632
+
633
+ ::-webkit-scrollbar-thumb {
634
+ background: var(--primary-color);
635
+ border-radius: 4px;
636
+ }
637
+
638
+ ::-webkit-scrollbar-thumb:hover {
639
+ background: var(--primary-hover);
640
+ }
641
+ """
642
+
643
+ # =============================================================================
644
+ # UI COMPONENTS
645
+ # =============================================================================
646
+
647
+ def create_header() -> gr.HTML:
648
+ """Create the application header."""
649
+ return gr.HTML(f"""
650
+ <h1>🌐 {APP_TITLE}</h1>
651
+ <p>{APP_DESCRIPTION}</p>
652
+ """)
653
+
654
+ def create_model_info_cards() -> gr.HTML:
655
+ """Create model information cards."""
656
+ cards_html = '<div class="model-info">'
657
+
658
+ for display_name, model_key in MODEL_CHOICES:
659
+ model_name = display_name.split(" ")[0] # Extract short name
660
+ description = MODEL_DESCRIPTIONS[model_key]
661
+
662
+ cards_html += f"""
663
+ <div class="model-card">
664
+ <h3>{model_name}</h3>
665
+ <p>{description}</p>
666
+ </div>
667
+ """
668
+
669
+ cards_html += '</div>'
670
+ return gr.HTML(cards_html)
671
+
672
+ def create_examples_section() -> gr.Examples:
673
+ """Create examples section with sample questions."""
674
+ return gr.Examples(
675
+ examples=[[example] for example in EXAMPLE_QUESTIONS],
676
+ inputs=None, # Will be set when creating the interface
677
+ label="πŸ’‘ Try these example sentences:",
678
+ examples_per_page=5
679
+ )
680
+
681
+ # =============================================================================
682
+ # APPLICATION SETUP
683
+ # =============================================================================
684
+
685
+ def create_gradio_interface() -> gr.Blocks:
686
+ """Create and configure the Gradio interface."""
687
+
688
+ # Initialize services
689
+ model_manager = ModelManager()
690
+ translation_service = TranslationService(model_manager)
691
+
692
+ # Initialize models
693
+ model_manager.initialize_models()
694
+
695
+ # Create Gradio interface
696
+ with gr.Blocks(
697
+ theme="soft",
698
+ title=APP_TITLE,
699
+ css=get_custom_css()
700
+ ) as demo:
701
+
702
+ # Header section
703
+ with gr.Column(elem_classes=["header"]):
704
+ create_header()
705
+
706
+ # Main content
707
+ with gr.Column(elem_classes=["main-container"]):
708
+
709
+ # Model selection
710
+ with gr.Row(elem_classes=["model-section"]):
711
+ model_choice = gr.Dropdown(
712
+ choices=MODEL_CHOICES,
713
+ label="πŸ€– Select Translation Model",
714
+ value=DEFAULT_MODEL,
715
+ elem_classes=["gr-dropdown"],
716
+ info="Choose the translation approach that best fits your needs"
717
+ )
718
+
719
+ # Input/Output section
720
+ with gr.Row(elem_classes=["io-section"]):
721
+ with gr.Column(elem_classes=["input-section"]):
722
+ gr.HTML('<div class="section-title">πŸ“ Input Text (English)</div>')
723
+ input_text = gr.Textbox(
724
+ placeholder="Enter your English text here or click on an example below...",
725
+ lines=6,
726
+ max_lines=10,
727
+ elem_classes=["gr-textbox"],
728
+ show_label=False,
729
+ container=False
730
+ )
731
+
732
+ with gr.Column(elem_classes=["output-section"]):
733
+ gr.HTML('<div class="section-title">πŸ‡»πŸ‡³ Translation (Vietnamese)</div>')
734
+ output_text = gr.Textbox(
735
+ placeholder="Translation will appear here...",
736
+ lines=6,
737
+ max_lines=10,
738
+ elem_classes=["gr-textbox"],
739
+ interactive=False,
740
+ show_label=False,
741
+ container=False
742
+ )
743
+
744
+ # Examples section
745
+ with gr.Row(elem_classes=["examples-section"]):
746
+ examples = gr.Examples(
747
+ examples=[[example] for example in EXAMPLE_QUESTIONS],
748
+ inputs=[input_text],
749
+ label="πŸ’‘ Try these example sentences:",
750
+ examples_per_page=5,
751
+ run_on_click=False
752
+ )
753
+
754
+ # Action buttons
755
+ with gr.Row():
756
+ translate_button = gr.Button(
757
+ "πŸš€ Translate Text",
758
+ elem_classes=["translate-button"],
759
+ variant="primary",
760
+ size="lg",
761
+ scale=3
762
+ )
763
+ clear_button = gr.Button(
764
+ "πŸ—‘οΈ Clear",
765
+ variant="secondary",
766
+ size="lg",
767
+ scale=1
768
+ )
769
+
770
+ # Model information cards
771
+ create_model_info_cards()
772
+
773
+ # Bind the translation function to the button
774
+ translate_button.click(
775
+ fn=translation_service.translate,
776
+ inputs=[model_choice, input_text],
777
+ outputs=output_text,
778
+ show_progress=True
779
+ )
780
+
781
+ # Clear button functionality
782
+ clear_button.click(
783
+ fn=lambda: ("", ""),
784
+ inputs=[],
785
+ outputs=[input_text, output_text]
786
+ )
787
+
788
+ # Also allow Enter key to trigger translation
789
+ input_text.submit(
790
+ fn=translation_service.translate,
791
+ inputs=[model_choice, input_text],
792
+ outputs=output_text,
793
+ show_progress=True
794
+ )
795
+
796
+ return demo
797
+
798
+ # =============================================================================
799
+ # MAIN EXECUTION
800
+ # =============================================================================
801
+
802
+ def main():
803
+ """Main function to run the application."""
804
+ try:
805
+ demo = create_gradio_interface()
806
+ demo.launch(
807
+ server_name="0.0.0.0",
808
+ server_port=7860,
809
+ share=False,
810
+ show_error=True
811
+ )
812
+ except Exception as e:
813
+ logger.error(f"Failed to launch application: {str(e)}")
814
+ raise
815
+
816
+ if __name__ == "__main__":
817
+ main()