Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
"""
|
| 2 |
LocaleNLP Translation Service
|
| 3 |
============================
|
| 4 |
-
|
| 5 |
A multi-language translation application supporting English, Wolof, Hausa, and Darija.
|
| 6 |
Features text, audio, and document translation with automatic chaining for all language pairs.
|
| 7 |
-
|
| 8 |
Author: LocaleNLP
|
| 9 |
"""
|
| 10 |
|
|
@@ -16,6 +15,8 @@ from typing import Optional, Dict, Tuple, Any, Union
|
|
| 16 |
from pathlib import Path
|
| 17 |
from dataclasses import dataclass
|
| 18 |
from enum import Enum
|
|
|
|
|
|
|
| 19 |
|
| 20 |
import gradio as gr
|
| 21 |
import torch
|
|
@@ -466,6 +467,64 @@ class TranslationApp:
|
|
| 466 |
|
| 467 |
return ""
|
| 468 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
def create_interface(self) -> gr.Blocks:
|
| 470 |
"""Create and return the Gradio interface."""
|
| 471 |
|
|
@@ -536,7 +595,36 @@ class TranslationApp:
|
|
| 536 |
lines=10,
|
| 537 |
interactive=False
|
| 538 |
)
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
# Event handlers
|
| 541 |
def update_visibility(mode: str) -> Dict[str, Any]:
|
| 542 |
"""Update component visibility based on input mode."""
|
|
@@ -569,24 +657,61 @@ class TranslationApp:
|
|
| 569 |
logger.error(f"Processing error: {e}")
|
| 570 |
return "", f"❌ Error: {str(e)}"
|
| 571 |
|
| 572 |
-
def
|
| 573 |
extracted_text: str,
|
| 574 |
source_lang: str,
|
| 575 |
target_lang: str
|
| 576 |
-
) -> str:
|
| 577 |
-
"""Handle translation
|
| 578 |
if not extracted_text.strip():
|
| 579 |
-
return "📝 No text to translate."
|
| 580 |
try:
|
| 581 |
-
|
| 582 |
extracted_text,
|
| 583 |
Language(source_lang),
|
| 584 |
Language(target_lang)
|
| 585 |
)
|
|
|
|
| 586 |
except Exception as e:
|
| 587 |
logger.error(f"Translation error: {e}")
|
| 588 |
-
|
| 589 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
# Connect events
|
| 591 |
input_mode.change(
|
| 592 |
fn=update_visibility,
|
|
@@ -594,14 +719,35 @@ class TranslationApp:
|
|
| 594 |
outputs=[input_text, audio_input, file_input, extracted_text, output_text]
|
| 595 |
)
|
| 596 |
|
| 597 |
-
|
|
|
|
| 598 |
fn=handle_process,
|
| 599 |
inputs=[input_mode, input_lang, input_text, audio_input, file_input],
|
| 600 |
outputs=[extracted_text, output_text]
|
| 601 |
-
)
|
| 602 |
-
|
|
|
|
|
|
|
| 603 |
inputs=[extracted_text, input_lang, output_lang],
|
| 604 |
-
outputs=output_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
)
|
| 606 |
|
| 607 |
return interface
|
|
|
|
| 1 |
"""
|
| 2 |
LocaleNLP Translation Service
|
| 3 |
============================
|
|
|
|
| 4 |
A multi-language translation application supporting English, Wolof, Hausa, and Darija.
|
| 5 |
Features text, audio, and document translation with automatic chaining for all language pairs.
|
| 6 |
+
Includes user feedback logging to CSV.
|
| 7 |
Author: LocaleNLP
|
| 8 |
"""
|
| 9 |
|
|
|
|
| 15 |
from pathlib import Path
|
| 16 |
from dataclasses import dataclass
|
| 17 |
from enum import Enum
|
| 18 |
+
import csv
|
| 19 |
+
from datetime import datetime
|
| 20 |
|
| 21 |
import gradio as gr
|
| 22 |
import torch
|
|
|
|
| 467 |
|
| 468 |
return ""
|
| 469 |
|
| 470 |
+
def log_feedback(
|
| 471 |
+
self,
|
| 472 |
+
source_lang: Language,
|
| 473 |
+
target_lang: Language,
|
| 474 |
+
user_input: str,
|
| 475 |
+
model_output: str,
|
| 476 |
+
notation: Optional[float] = None,
|
| 477 |
+
correct_translation: Optional[str] = None
|
| 478 |
+
):
|
| 479 |
+
"""
|
| 480 |
+
Log user feedback to a CSV file named '{source}_{target}.csv'.
|
| 481 |
+
|
| 482 |
+
Args:
|
| 483 |
+
source_lang: Source language enum
|
| 484 |
+
target_lang: Target language enum
|
| 485 |
+
user_input: Original input text
|
| 486 |
+
model_output: Model-generated translation
|
| 487 |
+
notation: User rating (optional)
|
| 488 |
+
correct_translation: Corrected version (optional)
|
| 489 |
+
"""
|
| 490 |
+
# Define filename
|
| 491 |
+
src = source_lang.value.lower()
|
| 492 |
+
tgt = target_lang.value.lower()
|
| 493 |
+
filename = f"{src}_{tgt}.csv"
|
| 494 |
+
|
| 495 |
+
# Define headers and row
|
| 496 |
+
headers = [
|
| 497 |
+
"timestamp",
|
| 498 |
+
"source_language",
|
| 499 |
+
"target_language",
|
| 500 |
+
"user_input",
|
| 501 |
+
"model_output",
|
| 502 |
+
"notation",
|
| 503 |
+
"correct_translation"
|
| 504 |
+
]
|
| 505 |
+
row = {
|
| 506 |
+
"timestamp": datetime.now().isoformat(),
|
| 507 |
+
"source_language": source_lang.value,
|
| 508 |
+
"target_language": target_lang.value,
|
| 509 |
+
"user_input": user_input.strip(),
|
| 510 |
+
"model_output": model_output.strip(),
|
| 511 |
+
"notation": notation,
|
| 512 |
+
"correct_translation": correct_translation.strip() if correct_translation else ""
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
# Check if file exists to determine whether to write headers
|
| 516 |
+
file_exists = Path(filename).exists()
|
| 517 |
+
|
| 518 |
+
try:
|
| 519 |
+
with open(filename, mode="a", encoding="utf-8", newline="") as f:
|
| 520 |
+
writer = csv.DictWriter(f, fieldnames=headers)
|
| 521 |
+
if not file_exists:
|
| 522 |
+
writer.writeheader() # Write header only once
|
| 523 |
+
writer.writerow(row)
|
| 524 |
+
logger.info(f"Feedback saved to {filename}")
|
| 525 |
+
except Exception as e:
|
| 526 |
+
logger.error(f"Failed to save feedback to {filename}: {e}")
|
| 527 |
+
|
| 528 |
def create_interface(self) -> gr.Blocks:
|
| 529 |
"""Create and return the Gradio interface."""
|
| 530 |
|
|
|
|
| 595 |
lines=10,
|
| 596 |
interactive=False
|
| 597 |
)
|
| 598 |
+
|
| 599 |
+
# --- FEEDBACK SECTION ---
|
| 600 |
+
gr.Markdown("### 📝 Provide Feedback on Translation")
|
| 601 |
+
|
| 602 |
+
with gr.Row():
|
| 603 |
+
notation = gr.Slider(
|
| 604 |
+
minimum=1,
|
| 605 |
+
maximum=5,
|
| 606 |
+
step=1,
|
| 607 |
+
label="Rate translation quality (1-5 stars)",
|
| 608 |
+
value=None,
|
| 609 |
+
interactive=True
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
correct_translation = gr.Textbox(
|
| 613 |
+
label="Correct Translation (if incorrect)",
|
| 614 |
+
placeholder="Please provide the correct version if inaccurate...",
|
| 615 |
+
lines=4,
|
| 616 |
+
value=""
|
| 617 |
+
)
|
| 618 |
+
|
| 619 |
+
submit_feedback = gr.Button("📤 Submit Feedback", variant="primary")
|
| 620 |
+
feedback_status = gr.Textbox(label="Feedback Status", value="", interactive=False)
|
| 621 |
+
|
| 622 |
+
# Hidden states to preserve context for feedback
|
| 623 |
+
user_input_state = gr.State()
|
| 624 |
+
model_output_state = gr.State()
|
| 625 |
+
source_lang_state = gr.State()
|
| 626 |
+
target_lang_state = gr.State()
|
| 627 |
+
|
| 628 |
# Event handlers
|
| 629 |
def update_visibility(mode: str) -> Dict[str, Any]:
|
| 630 |
"""Update component visibility based on input mode."""
|
|
|
|
| 657 |
logger.error(f"Processing error: {e}")
|
| 658 |
return "", f"❌ Error: {str(e)}"
|
| 659 |
|
| 660 |
+
def handle_translate_with_input(
|
| 661 |
extracted_text: str,
|
| 662 |
source_lang: str,
|
| 663 |
target_lang: str
|
| 664 |
+
) -> Tuple[str, str, str]:
|
| 665 |
+
"""Handle translation and return inputs for logging."""
|
| 666 |
if not extracted_text.strip():
|
| 667 |
+
return "📝 No text to translate.", "", ""
|
| 668 |
try:
|
| 669 |
+
translated = self.translation_service.translate(
|
| 670 |
extracted_text,
|
| 671 |
Language(source_lang),
|
| 672 |
Language(target_lang)
|
| 673 |
)
|
| 674 |
+
return translated, extracted_text, source_lang
|
| 675 |
except Exception as e:
|
| 676 |
logger.error(f"Translation error: {e}")
|
| 677 |
+
error_msg = f"❌ Translation error: {str(e)}"
|
| 678 |
+
return error_msg, extracted_text, source_lang
|
| 679 |
+
|
| 680 |
+
def set_states_for_feedback(
|
| 681 |
+
output: str,
|
| 682 |
+
inp: str,
|
| 683 |
+
src: str,
|
| 684 |
+
tgt: str
|
| 685 |
+
):
|
| 686 |
+
"""Set hidden states for feedback submission."""
|
| 687 |
+
return inp, output, src, tgt
|
| 688 |
+
|
| 689 |
+
def save_feedback(
|
| 690 |
+
user_input: str,
|
| 691 |
+
model_output: str,
|
| 692 |
+
source_lang_str: str,
|
| 693 |
+
target_lang_str: str,
|
| 694 |
+
notation_val: float,
|
| 695 |
+
correct_trans: str
|
| 696 |
+
):
|
| 697 |
+
"""Save feedback to CSV."""
|
| 698 |
+
try:
|
| 699 |
+
source_lang = Language(source_lang_str)
|
| 700 |
+
target_lang = Language(target_lang_str)
|
| 701 |
+
|
| 702 |
+
self.log_feedback(
|
| 703 |
+
source_lang=source_lang,
|
| 704 |
+
target_lang=target_lang,
|
| 705 |
+
user_input=user_input,
|
| 706 |
+
model_output=model_output,
|
| 707 |
+
notation=notation_val,
|
| 708 |
+
correct_translation=correct_trans or None
|
| 709 |
+
)
|
| 710 |
+
return "✅ Thank you for your feedback!"
|
| 711 |
+
except Exception as e:
|
| 712 |
+
logger.error(f"Feedback submission failed: {e}")
|
| 713 |
+
return f"❌ Failed to save feedback: {str(e)}"
|
| 714 |
+
|
| 715 |
# Connect events
|
| 716 |
input_mode.change(
|
| 717 |
fn=update_visibility,
|
|
|
|
| 719 |
outputs=[input_text, audio_input, file_input, extracted_text, output_text]
|
| 720 |
)
|
| 721 |
|
| 722 |
+
# Chain: Process → Translate → Store context → Wait for feedback
|
| 723 |
+
process_event = translate_btn.click(
|
| 724 |
fn=handle_process,
|
| 725 |
inputs=[input_mode, input_lang, input_text, audio_input, file_input],
|
| 726 |
outputs=[extracted_text, output_text]
|
| 727 |
+
)
|
| 728 |
+
|
| 729 |
+
process_event.success(
|
| 730 |
+
fn=handle_translate_with_input,
|
| 731 |
inputs=[extracted_text, input_lang, output_lang],
|
| 732 |
+
outputs=[output_text, gr.State(), gr.State()]
|
| 733 |
+
).success(
|
| 734 |
+
fn=set_states_for_feedback,
|
| 735 |
+
inputs=[output_text, extracted_text, input_lang, output_lang],
|
| 736 |
+
outputs=[user_input_state, model_output_state, source_lang_state, target_lang_state]
|
| 737 |
+
)
|
| 738 |
+
|
| 739 |
+
# Feedback submission
|
| 740 |
+
submit_feedback.click(
|
| 741 |
+
fn=save_feedback,
|
| 742 |
+
inputs=[
|
| 743 |
+
user_input_state,
|
| 744 |
+
model_output_state,
|
| 745 |
+
source_lang_state,
|
| 746 |
+
target_lang_state,
|
| 747 |
+
notation,
|
| 748 |
+
correct_translation
|
| 749 |
+
],
|
| 750 |
+
outputs=feedback_status
|
| 751 |
)
|
| 752 |
|
| 753 |
return interface
|