system HF Staff commited on
Commit
c7d6cb1
·
verified ·
1 Parent(s): 601d008

Deploy ae593cc from hugging_face

Browse files
Files changed (1) hide show
  1. app.py +46 -43
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
- TRANSLATION_CHOICES = ["google", "gemini", "nllb"]
 
 
33
 
34
  LANGUAGES = {
35
  "None (keep original)": "",
@@ -41,10 +43,22 @@ LANGUAGES = {
41
  "German": "de",
42
  }
43
 
44
- # The NLLB fine-tune (EzekielMW/LuoKslGloss) was only trained on these targets,
45
- # and KSL gloss is not something the general-purpose translators can produce.
46
- NLLB_LANGUAGES = {"sw", "swa", "luo", "ksl", "en", "eng"}
47
- NLLB_ONLY_LANGUAGES = {"ksl"}
 
 
 
 
 
 
 
 
 
 
 
 
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, translation_provider, language_label):
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
- check_language(translation_provider, target_language)
230
 
231
- controller = get_controller(llm_provider, translation_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, translation_provider):
260
  state = sync_table(state, rows)
261
  if not state.get("sentences"):
262
  raise gr.Error("Run Simplify first.")
263
 
264
- controller = get_controller(llm_provider, translation_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,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, translation_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
- controller = get_controller(llm_provider, translation_provider)
 
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, translation_provider, symbolset):
327
  state = sync_table(state, rows)
328
  if not state.get("sentences"):
329
  raise gr.Error("Run Simplify first.")
330
 
331
- controller = get_controller(llm_provider, translation_provider)
 
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, translation_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
- controller = get_controller(llm_provider, translation_provider)
 
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
- translation_provider = gr.Dropdown(
438
- TRANSLATION_CHOICES,
439
- value="google",
440
- label="Translation",
441
- info="'nllb' loads a 1.3B model on first use — the only option for "
442
- "Luo and KSL gloss.",
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, translation_provider, language],
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, translation_provider],
527
  outputs=[state, feedback_out],
528
  )
529
  revise_btn.click(
530
  step_revise,
531
- inputs=[state, table, llm_provider, translation_provider],
532
  outputs=[state, table],
533
  )
534
  illustrate_btn.click(
535
  step_find_symbols,
536
- inputs=[state, table, llm_provider, translation_provider, symbolset],
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, translation_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
  )