Spaces:
Sleeping
Sleeping
Deploy ae593cc from hugging_face
Browse files
app.py
CHANGED
|
@@ -29,7 +29,9 @@ ICON_DIR = BASE_DIR / "src" / "temp"
|
|
| 29 |
ICON_DIR.mkdir(parents=True, exist_ok=True)
|
| 30 |
|
| 31 |
LLM_CHOICES = ["hf", "llama", "gemini", "bedrock"]
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
LANGUAGES = {
|
| 35 |
"None (keep original)": "",
|
|
@@ -41,10 +43,22 @@ LANGUAGES = {
|
|
| 41 |
"German": "de",
|
| 42 |
}
|
| 43 |
|
| 44 |
-
#
|
| 45 |
-
#
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
VOICES = ["af_heart", "af_bella", "am_michael", "bf_lily", "bm_george"]
|
| 50 |
|
|
@@ -85,22 +99,6 @@ def get_tts():
|
|
| 85 |
return _tts
|
| 86 |
|
| 87 |
|
| 88 |
-
def check_language(translation_provider: str, code: str) -> None:
|
| 89 |
-
"""Reject provider/language pairs that would silently return untranslated text."""
|
| 90 |
-
if not code:
|
| 91 |
-
return
|
| 92 |
-
if translation_provider == "nllb" and code not in NLLB_LANGUAGES:
|
| 93 |
-
raise gr.Error(
|
| 94 |
-
"The NLLB model only translates into Swahili, Luo and KSL gloss. "
|
| 95 |
-
"Pick one of those, or switch Translation to 'google'."
|
| 96 |
-
)
|
| 97 |
-
if code in NLLB_ONLY_LANGUAGES and translation_provider != "nllb":
|
| 98 |
-
raise gr.Error(
|
| 99 |
-
"Kenyan Sign Language gloss is only produced by the 'nllb' "
|
| 100 |
-
"translation provider. Switch Translation to 'nllb'."
|
| 101 |
-
)
|
| 102 |
-
|
| 103 |
-
|
| 104 |
def read_upload(file_path: str | None) -> str:
|
| 105 |
"""Extract plain text from an uploaded .txt/.md/.docx/.pdf document."""
|
| 106 |
if not file_path:
|
|
@@ -221,14 +219,14 @@ def render_sentences(state: dict) -> str:
|
|
| 221 |
return f'<h2 style="margin-bottom:14px;">{title}</h2>' + "".join(rows)
|
| 222 |
|
| 223 |
|
| 224 |
-
def step_simplify(text, llm_provider,
|
| 225 |
if not text or not text.strip():
|
| 226 |
raise gr.Error("Add some text — upload a document or paste it above.")
|
| 227 |
|
| 228 |
target_language = LANGUAGES.get(language_label, "")
|
| 229 |
-
|
| 230 |
|
| 231 |
-
controller = get_controller(llm_provider,
|
| 232 |
result = controller.simplify_text(text, target_language=target_language or None)
|
| 233 |
if "error" in result:
|
| 234 |
raise gr.Error(f"The model did not return valid JSON: {result['error']}")
|
|
@@ -238,6 +236,7 @@ def step_simplify(text, llm_provider, translation_provider, language_label):
|
|
| 238 |
"sentences": result.get("simplified_sentences", []),
|
| 239 |
"original": text,
|
| 240 |
"language": target_language,
|
|
|
|
| 241 |
"request_id": None,
|
| 242 |
"feedback": "",
|
| 243 |
}
|
|
@@ -256,12 +255,13 @@ def step_simplify(text, llm_provider, translation_provider, language_label):
|
|
| 256 |
)
|
| 257 |
|
| 258 |
|
| 259 |
-
def step_validate(state, rows, llm_provider,
|
| 260 |
state = sync_table(state, rows)
|
| 261 |
if not state.get("sentences"):
|
| 262 |
raise gr.Error("Run Simplify first.")
|
| 263 |
|
| 264 |
-
|
|
|
|
| 265 |
result = controller.validate_text(state["original"], state["sentences"])
|
| 266 |
if "error" in result:
|
| 267 |
raise gr.Error(f"Validation did not return valid JSON: {result['error']}")
|
|
@@ -282,12 +282,13 @@ def step_validate(state, rows, llm_provider, translation_provider):
|
|
| 282 |
return state, banner + detail
|
| 283 |
|
| 284 |
|
| 285 |
-
def step_revise(state, rows, llm_provider,
|
| 286 |
state = sync_table(state, rows)
|
| 287 |
if not state.get("feedback"):
|
| 288 |
raise gr.Error("Run Validate first — Revise consumes its feedback.")
|
| 289 |
|
| 290 |
-
|
|
|
|
| 291 |
result = controller.revise_text(
|
| 292 |
original_text=state["original"],
|
| 293 |
easy_read_sentences=state["sentences"],
|
|
@@ -323,12 +324,13 @@ def _generate_button_update(sentences: list[dict]):
|
|
| 323 |
return gr.update(visible=True, value=label)
|
| 324 |
|
| 325 |
|
| 326 |
-
def step_find_symbols(state, rows, llm_provider,
|
| 327 |
state = sync_table(state, rows)
|
| 328 |
if not state.get("sentences"):
|
| 329 |
raise gr.Error("Run Simplify first.")
|
| 330 |
|
| 331 |
-
|
|
|
|
| 332 |
result = controller.search_symbols(state["sentences"], symbolset=symbolset)
|
| 333 |
state["request_id"] = result["request_id"]
|
| 334 |
for sentence, found in zip(state["sentences"], result["results"]):
|
|
@@ -338,7 +340,7 @@ def step_find_symbols(state, rows, llm_provider, translation_provider, symbolset
|
|
| 338 |
return state, render_sentences(state), _generate_button_update(state["sentences"])
|
| 339 |
|
| 340 |
|
| 341 |
-
def step_generate_missing(state, rows, llm_provider,
|
| 342 |
state = sync_table(state, rows)
|
| 343 |
if not state.get("sentences"):
|
| 344 |
raise gr.Error("Run Simplify first.")
|
|
@@ -348,7 +350,8 @@ def step_generate_missing(state, rows, llm_provider, translation_provider):
|
|
| 348 |
gr.Info("Every sentence already has a symbol — nothing to generate.")
|
| 349 |
return state, render_sentences(state), _generate_button_update(state["sentences"])
|
| 350 |
|
| 351 |
-
|
|
|
|
| 352 |
# Generation only — no symbol lookup (Find symbols is the separate search
|
| 353 |
# step). Uses the current, table-synced prompt for each missing sentence.
|
| 354 |
controller.generate_icons(misses, use_global_symbols=False)
|
|
@@ -434,12 +437,12 @@ with gr.Blocks(title="GenAI for Easy Read") as demo:
|
|
| 434 |
info="'hf' runs Llama via Hugging Face Inference (needs HF_TOKEN). "
|
| 435 |
"'llama'/'bedrock' use AWS; 'gemini' uses a Google key.",
|
| 436 |
)
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
value="
|
| 440 |
-
label="Translation",
|
| 441 |
-
info="
|
| 442 |
-
"
|
| 443 |
)
|
| 444 |
language = gr.Dropdown(
|
| 445 |
list(LANGUAGES), value="None (keep original)", label="Translate into"
|
|
@@ -516,31 +519,31 @@ with gr.Blocks(title="GenAI for Easy Read") as demo:
|
|
| 516 |
upload.upload(read_upload, inputs=[upload], outputs=[text_input])
|
| 517 |
simplify_btn.click(
|
| 518 |
step_simplify,
|
| 519 |
-
inputs=[text_input, llm_provider,
|
| 520 |
outputs=[state, table, feedback_out, review_group, preview, empty_hint,
|
| 521 |
export_group, generate_missing_btn],
|
| 522 |
scroll_to_output=True,
|
| 523 |
)
|
| 524 |
validate_btn.click(
|
| 525 |
step_validate,
|
| 526 |
-
inputs=[state, table, llm_provider,
|
| 527 |
outputs=[state, feedback_out],
|
| 528 |
)
|
| 529 |
revise_btn.click(
|
| 530 |
step_revise,
|
| 531 |
-
inputs=[state, table, llm_provider,
|
| 532 |
outputs=[state, table],
|
| 533 |
)
|
| 534 |
illustrate_btn.click(
|
| 535 |
step_find_symbols,
|
| 536 |
-
inputs=[state, table, llm_provider,
|
| 537 |
outputs=[state, preview, generate_missing_btn],
|
| 538 |
# Auto-scroll to the results so they aren't lost below the fold.
|
| 539 |
scroll_to_output=True,
|
| 540 |
)
|
| 541 |
generate_missing_btn.click(
|
| 542 |
step_generate_missing,
|
| 543 |
-
inputs=[state, table, llm_provider,
|
| 544 |
outputs=[state, preview, generate_missing_btn],
|
| 545 |
scroll_to_output=True,
|
| 546 |
)
|
|
|
|
| 29 |
ICON_DIR.mkdir(parents=True, exist_ok=True)
|
| 30 |
|
| 31 |
LLM_CHOICES = ["hf", "llama", "gemini", "bedrock"]
|
| 32 |
+
# Translation engine: 'Automatic' routes by target language; 'Gemini' uses Gemini
|
| 33 |
+
# for everything (except KSL gloss, which only NLLB can produce).
|
| 34 |
+
TRANSLATION_ENGINES = ["Automatic", "Gemini"]
|
| 35 |
|
| 36 |
LANGUAGES = {
|
| 37 |
"None (keep original)": "",
|
|
|
|
| 43 |
"German": "de",
|
| 44 |
}
|
| 45 |
|
| 46 |
+
# Per-language routing (matches the Phase II report): the local NLLB-200 fine-tune
|
| 47 |
+
# handles Swahili and Luo; Google Translate handles French/Spanish/German. KSL
|
| 48 |
+
# gloss is NLLB-only.
|
| 49 |
+
_NLLB_ROUTED = {"sw", "swa", "luo"}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def resolve_translation_provider(engine: str, code: str) -> str:
|
| 53 |
+
"""Choose the translation backend for a target language code."""
|
| 54 |
+
code = (code or "").lower()
|
| 55 |
+
if not code:
|
| 56 |
+
return "google" # no translation requested; provider is unused
|
| 57 |
+
if code == "ksl":
|
| 58 |
+
return "nllb" # only NLLB produces sign-language gloss
|
| 59 |
+
if engine == "Gemini":
|
| 60 |
+
return "gemini" # Gemini alternative for all other languages
|
| 61 |
+
return "nllb" if code in _NLLB_ROUTED else "google"
|
| 62 |
|
| 63 |
VOICES = ["af_heart", "af_bella", "am_michael", "bf_lily", "bm_george"]
|
| 64 |
|
|
|
|
| 99 |
return _tts
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
def read_upload(file_path: str | None) -> str:
|
| 103 |
"""Extract plain text from an uploaded .txt/.md/.docx/.pdf document."""
|
| 104 |
if not file_path:
|
|
|
|
| 219 |
return f'<h2 style="margin-bottom:14px;">{title}</h2>' + "".join(rows)
|
| 220 |
|
| 221 |
|
| 222 |
+
def step_simplify(text, llm_provider, translation_engine, language_label):
|
| 223 |
if not text or not text.strip():
|
| 224 |
raise gr.Error("Add some text — upload a document or paste it above.")
|
| 225 |
|
| 226 |
target_language = LANGUAGES.get(language_label, "")
|
| 227 |
+
provider = resolve_translation_provider(translation_engine, target_language)
|
| 228 |
|
| 229 |
+
controller = get_controller(llm_provider, provider)
|
| 230 |
result = controller.simplify_text(text, target_language=target_language or None)
|
| 231 |
if "error" in result:
|
| 232 |
raise gr.Error(f"The model did not return valid JSON: {result['error']}")
|
|
|
|
| 236 |
"sentences": result.get("simplified_sentences", []),
|
| 237 |
"original": text,
|
| 238 |
"language": target_language,
|
| 239 |
+
"translation_engine": translation_engine,
|
| 240 |
"request_id": None,
|
| 241 |
"feedback": "",
|
| 242 |
}
|
|
|
|
| 255 |
)
|
| 256 |
|
| 257 |
|
| 258 |
+
def step_validate(state, rows, llm_provider, translation_engine):
|
| 259 |
state = sync_table(state, rows)
|
| 260 |
if not state.get("sentences"):
|
| 261 |
raise gr.Error("Run Simplify first.")
|
| 262 |
|
| 263 |
+
provider = resolve_translation_provider(translation_engine, state.get("language", ""))
|
| 264 |
+
controller = get_controller(llm_provider, provider)
|
| 265 |
result = controller.validate_text(state["original"], state["sentences"])
|
| 266 |
if "error" in result:
|
| 267 |
raise gr.Error(f"Validation did not return valid JSON: {result['error']}")
|
|
|
|
| 282 |
return state, banner + detail
|
| 283 |
|
| 284 |
|
| 285 |
+
def step_revise(state, rows, llm_provider, translation_engine):
|
| 286 |
state = sync_table(state, rows)
|
| 287 |
if not state.get("feedback"):
|
| 288 |
raise gr.Error("Run Validate first — Revise consumes its feedback.")
|
| 289 |
|
| 290 |
+
provider = resolve_translation_provider(translation_engine, state.get("language", ""))
|
| 291 |
+
controller = get_controller(llm_provider, provider)
|
| 292 |
result = controller.revise_text(
|
| 293 |
original_text=state["original"],
|
| 294 |
easy_read_sentences=state["sentences"],
|
|
|
|
| 324 |
return gr.update(visible=True, value=label)
|
| 325 |
|
| 326 |
|
| 327 |
+
def step_find_symbols(state, rows, llm_provider, translation_engine, symbolset):
|
| 328 |
state = sync_table(state, rows)
|
| 329 |
if not state.get("sentences"):
|
| 330 |
raise gr.Error("Run Simplify first.")
|
| 331 |
|
| 332 |
+
provider = resolve_translation_provider(translation_engine, state.get("language", ""))
|
| 333 |
+
controller = get_controller(llm_provider, provider)
|
| 334 |
result = controller.search_symbols(state["sentences"], symbolset=symbolset)
|
| 335 |
state["request_id"] = result["request_id"]
|
| 336 |
for sentence, found in zip(state["sentences"], result["results"]):
|
|
|
|
| 340 |
return state, render_sentences(state), _generate_button_update(state["sentences"])
|
| 341 |
|
| 342 |
|
| 343 |
+
def step_generate_missing(state, rows, llm_provider, translation_engine):
|
| 344 |
state = sync_table(state, rows)
|
| 345 |
if not state.get("sentences"):
|
| 346 |
raise gr.Error("Run Simplify first.")
|
|
|
|
| 350 |
gr.Info("Every sentence already has a symbol — nothing to generate.")
|
| 351 |
return state, render_sentences(state), _generate_button_update(state["sentences"])
|
| 352 |
|
| 353 |
+
provider = resolve_translation_provider(translation_engine, state.get("language", ""))
|
| 354 |
+
controller = get_controller(llm_provider, provider)
|
| 355 |
# Generation only — no symbol lookup (Find symbols is the separate search
|
| 356 |
# step). Uses the current, table-synced prompt for each missing sentence.
|
| 357 |
controller.generate_icons(misses, use_global_symbols=False)
|
|
|
|
| 437 |
info="'hf' runs Llama via Hugging Face Inference (needs HF_TOKEN). "
|
| 438 |
"'llama'/'bedrock' use AWS; 'gemini' uses a Google key.",
|
| 439 |
)
|
| 440 |
+
translation_engine = gr.Dropdown(
|
| 441 |
+
TRANSLATION_ENGINES,
|
| 442 |
+
value="Automatic",
|
| 443 |
+
label="Translation engine",
|
| 444 |
+
info="Automatic routes by language: NLLB for Swahili/Luo/KSL, Google "
|
| 445 |
+
"for French/Spanish/German. Gemini translates all languages.",
|
| 446 |
)
|
| 447 |
language = gr.Dropdown(
|
| 448 |
list(LANGUAGES), value="None (keep original)", label="Translate into"
|
|
|
|
| 519 |
upload.upload(read_upload, inputs=[upload], outputs=[text_input])
|
| 520 |
simplify_btn.click(
|
| 521 |
step_simplify,
|
| 522 |
+
inputs=[text_input, llm_provider, translation_engine, language],
|
| 523 |
outputs=[state, table, feedback_out, review_group, preview, empty_hint,
|
| 524 |
export_group, generate_missing_btn],
|
| 525 |
scroll_to_output=True,
|
| 526 |
)
|
| 527 |
validate_btn.click(
|
| 528 |
step_validate,
|
| 529 |
+
inputs=[state, table, llm_provider, translation_engine],
|
| 530 |
outputs=[state, feedback_out],
|
| 531 |
)
|
| 532 |
revise_btn.click(
|
| 533 |
step_revise,
|
| 534 |
+
inputs=[state, table, llm_provider, translation_engine],
|
| 535 |
outputs=[state, table],
|
| 536 |
)
|
| 537 |
illustrate_btn.click(
|
| 538 |
step_find_symbols,
|
| 539 |
+
inputs=[state, table, llm_provider, translation_engine, symbolset],
|
| 540 |
outputs=[state, preview, generate_missing_btn],
|
| 541 |
# Auto-scroll to the results so they aren't lost below the fold.
|
| 542 |
scroll_to_output=True,
|
| 543 |
)
|
| 544 |
generate_missing_btn.click(
|
| 545 |
step_generate_missing,
|
| 546 |
+
inputs=[state, table, llm_provider, translation_engine],
|
| 547 |
outputs=[state, preview, generate_missing_btn],
|
| 548 |
scroll_to_output=True,
|
| 549 |
)
|