Spaces:
Running on Zero
Running on Zero
File size: 9,121 Bytes
88a619b 36fc86c 88a619b 04721fb 88a619b 04721fb 88a619b c75b0b8 448481b 23eb152 04721fb c75b0b8 88a619b 04721fb 88a619b 36fc86c 88a619b 36fc86c 88a619b 36fc86c 88a619b f944367 88a619b 36fc86c 88a619b 04721fb 88a619b 36fc86c 88a619b 04721fb 88a619b | 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 | """SimpleTranslationUI — plain-text Tibetan Buddhist translation editor."""
from dotenv import load_dotenv
load_dotenv()
import gradio as gr
from engine import DEFAULT_MODEL, list_model_choices
from styles import CSS
from handlers import (
MAX_SLOTS,
_handle_cancel_translate,
_handle_download_click,
_handle_get_files,
_handle_next,
_handle_prev,
_handle_save,
_load_resume_json,
_load_source,
_make_state,
_read_prompt,
_reset_prompt,
_save_prompt,
_set_usage_tracking,
_translate_all,
_translate_one,
)
def build_app() -> gr.Blocks:
with gr.Blocks(title="SimpleTranslationUI") as demo:
app_state = gr.State(_make_state())
gr.Markdown(
"# SimpleTranslationUI\nTibetan Buddhist Translation Editor\n\n"
"**How to use:** Upload a source document below — it's automatically split into "
"segments (by paragraph, then by sentence-ending shad marks or whitespace for long "
"paragraphs). Click **Translate All** to translate every segment, or translate one "
"segment at a time with its own **Translate** button. Edit any translation inline, "
"then **Download** the result as text, Word, or JSON.\n\n"
"**Choosing a model:** By default (no API key) translation runs on a local CPU "
"model ([billingsmoore/mlotsawa-ground-base]"
"(https://huggingface.co/billingsmoore/mlotsawa-ground-base)) — free, runs on this "
"machine, but a plain translation model, not an instruction-following AI (it ignores "
"the translation prompt below) and generally lower translation quality. For higher-quality, instruction-"
"following translation, add an [**OpenRouter API key**](https://openrouter.ai/keys) in "
"Settings and pick a model from the dropdown — this calls OpenRouter's API (usage is "
"billed to your OpenRouter account) and uses the editable translation prompt. See the "
"[Tibetan AI Leaderboard](https://huggingface.co/spaces/khyentsevision/tibetan-ai-leaderboard) "
"for how different models perform on Tibetan translation evaluations.\n\n"
"Usage of this space may be monitored for research purposes. You can opt out of this "
"in Settings."
)
with gr.Row():
source_input = gr.File(
label="Source Document (.txt, .docx, .pdf)",
file_types=[".txt", ".docx", ".pdf"],
scale=1,
)
resume_input = gr.File(
label="Resume: Previously downloaded JSON (optional)",
file_types=[".json"],
scale=1,
)
with gr.Accordion("Settings", open=False):
usage_tracking_checkbox = gr.Checkbox(
label="Allow usage tracking for research purposes",
value=True,
)
with gr.Row():
openrouter_api_key = gr.Textbox(
label="OpenRouter API Key (optional)",
placeholder="sk-or-… — leave blank to use the local CPU model instead",
type="password",
scale=2,
)
openrouter_model = gr.Dropdown(
choices=list_model_choices(), value=DEFAULT_MODEL,
label="OpenRouter Model (only used if a key is given)",
info="A few recommended models are pinned at the top; the rest of "
"OpenRouter's catalog is below — type to filter.",
scale=1,
)
gr.Markdown(
"### Translation Prompt\n"
"Only used when translating with OpenRouter — the local CPU fallback "
"(billingsmoore/mlotsawa-ground-base) is a plain translation model and ignores this. "
"Saved directly to `translation_prompt.txt` in this app's folder."
)
prompt_box = gr.Textbox(label="Translation Prompt", lines=12, value=_read_prompt())
with gr.Row():
save_prompt_btn = gr.Button("Save", variant="primary", size="sm", scale=0, min_width=80)
reset_prompt_btn = gr.Button("Reset to Default", size="sm", scale=0, min_width=130)
prompt_status = gr.Markdown("")
with gr.Row():
translate_all_btn = gr.Button("Translate All", variant="primary", scale=2)
cancel_btn = gr.Button("Cancel", variant="stop", scale=1)
status_output = gr.Textbox(label="Status", lines=2, interactive=False, visible=False)
with gr.Column(visible=False) as editor_col:
gr.Markdown("### Segments")
with gr.Row():
prev_btn = gr.Button("◀ Previous", interactive=False)
nav_label = gr.HTML("")
next_btn = gr.Button("Next ▶", interactive=False)
with gr.Row():
save_btn = gr.Button("Save", variant="secondary")
download_btn = gr.Button("Download", variant="secondary")
with gr.Row():
download_checklist = gr.CheckboxGroup(
label="Files to include", choices=[], value=[], visible=False, scale=2,
)
get_files_btn = gr.Button("Get Files", variant="primary", scale=0, min_width=100, visible=False)
download_zip = gr.File(label="Download", visible=False, interactive=False)
slot_groups, slot_sources, slot_targets, slot_translate_btns = [], [], [], []
for _ in range(MAX_SLOTS):
with gr.Group(visible=False) as grp:
with gr.Row():
tlb = gr.Button("Translate", size="sm", scale=0, min_width=90)
with gr.Row(equal_height=True):
src = gr.Textbox(label="Source", lines=3, scale=1, elem_classes=["src-box"])
tgt = gr.Textbox(label="Translation", lines=3, scale=1, elem_classes=["tgt-box"])
slot_groups.append(grp)
slot_sources.append(src)
slot_targets.append(tgt)
slot_translate_btns.append(tlb)
page_outputs = [
app_state, nav_label, prev_btn, next_btn,
*slot_groups, *slot_sources, *slot_targets,
]
edit_inputs = [app_state, *slot_sources, *slot_targets]
load_outputs = [*page_outputs, editor_col]
translate_all_outputs = [*page_outputs, status_output]
source_input.change(fn=_load_source, inputs=[source_input, app_state], outputs=load_outputs)
resume_input.change(fn=_load_resume_json, inputs=[resume_input, app_state], outputs=page_outputs)
prev_btn.click(_handle_prev, inputs=edit_inputs, outputs=page_outputs)
next_btn.click(_handle_next, inputs=edit_inputs, outputs=page_outputs)
translate_event = translate_all_btn.click(
_translate_all,
inputs=[app_state, openrouter_api_key, openrouter_model, *slot_sources, *slot_targets],
outputs=translate_all_outputs,
)
cancel_btn.click(_handle_cancel_translate, inputs=[app_state], outputs=[app_state], cancels=[translate_event])
save_btn.click(_handle_save, inputs=edit_inputs, outputs=[app_state, status_output])
download_btn.click(
_handle_download_click,
inputs=edit_inputs,
outputs=[app_state, download_checklist, get_files_btn, download_zip],
)
get_files_btn.click(
_handle_get_files,
inputs=[app_state, download_checklist],
outputs=[download_zip, download_checklist, get_files_btn],
)
for i in range(MAX_SLOTS):
slot_sources[i].blur(_handle_save, inputs=edit_inputs, outputs=[app_state, status_output])
slot_targets[i].blur(_handle_save, inputs=edit_inputs, outputs=[app_state, status_output])
for i in range(MAX_SLOTS):
slot_translate_btns[i].click(
fn=lambda: gr.update(value="", placeholder="Translating…"),
inputs=[],
outputs=[slot_targets[i]],
).then(
fn=lambda state, source, key, model, i=i: _translate_one(state, i, source, key, model),
inputs=[app_state, slot_sources[i], openrouter_api_key, openrouter_model],
outputs=[app_state, slot_targets[i]],
)
save_prompt_btn.click(_save_prompt, inputs=[app_state, prompt_box], outputs=[app_state, prompt_status])
reset_prompt_btn.click(_reset_prompt, inputs=[app_state], outputs=[app_state, prompt_box, prompt_status])
usage_tracking_checkbox.change(
_set_usage_tracking, inputs=[app_state, usage_tracking_checkbox], outputs=[app_state],
)
return demo
demo = build_app()
if __name__ == "__main__":
demo.launch(css=CSS, ssr_mode=False, server_name="0.0.0.0", server_port=7860)
else:
demo.css = CSS
|