Spaces:
Running
Running
Jiawei Dong commited on
Commit ·
9a40f1b
1
Parent(s): 067d7b2
eng updated
Browse files- README.md +1 -16
- __pycache__/api_client.cpython-310.pyc +0 -0
- api_client.py +3 -3
- app.py +76 -72
README.md
CHANGED
|
@@ -18,24 +18,9 @@ Patent translation demo for the public HIRO Translation API.
|
|
| 18 |
|
| 19 |
- **stream** — SSE streaming (`textOriginal` / `textTranslated` per segment)
|
| 20 |
- **fast** — single JSON with full `textTranslated`
|
| 21 |
-
-
|
| 22 |
- Auth: Bearer token via **Advanced → API Key** or env `HIRO_API_KEY`
|
| 23 |
|
| 24 |
-
Request body (aligned with terminal curl):
|
| 25 |
-
|
| 26 |
-
```json
|
| 27 |
-
{
|
| 28 |
-
"content": "本发明公开了一种档案管理文件储存用分类标识装置。",
|
| 29 |
-
"sourceLanguageCode": "zh",
|
| 30 |
-
"targetLanguageCode": "en",
|
| 31 |
-
"mode": "stream"
|
| 32 |
-
}
|
| 33 |
-
```
|
| 34 |
-
|
| 35 |
-
Internal stage URL (optional in Advanced):
|
| 36 |
-
|
| 37 |
-
`http://stage-s-patsnaprd-hiro-translation-api-internet.patsnap.info/compute/hiro_translation_api`
|
| 38 |
-
|
| 39 |
## Files
|
| 40 |
|
| 41 |
| File | Purpose |
|
|
|
|
| 18 |
|
| 19 |
- **stream** — SSE streaming (`textOriginal` / `textTranslated` per segment)
|
| 20 |
- **fast** — single JSON with full `textTranslated`
|
| 21 |
+
- Gateway (fixed): `https://connect.zhihuiya.com/hiro_translation`
|
| 22 |
- Auth: Bearer token via **Advanced → API Key** or env `HIRO_API_KEY`
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
## Files
|
| 25 |
|
| 26 |
| File | Purpose |
|
__pycache__/api_client.cpython-310.pyc
CHANGED
|
Binary files a/__pycache__/api_client.cpython-310.pyc and b/__pycache__/api_client.cpython-310.pyc differ
|
|
|
api_client.py
CHANGED
|
@@ -73,11 +73,11 @@ def health_ok(
|
|
| 73 |
r.raise_for_status()
|
| 74 |
data = r.json()
|
| 75 |
if data.get("status") != "OK":
|
| 76 |
-
return False, f"
|
| 77 |
upstream = data.get("upstream", "UNKNOWN")
|
| 78 |
if upstream == "OK":
|
| 79 |
-
return True, "
|
| 80 |
-
return False, f"
|
| 81 |
except requests.RequestException as exc:
|
| 82 |
return False, str(exc)
|
| 83 |
|
|
|
|
| 73 |
r.raise_for_status()
|
| 74 |
data = r.json()
|
| 75 |
if data.get("status") != "OK":
|
| 76 |
+
return False, f"Unexpected response: {r.text[:200]}"
|
| 77 |
upstream = data.get("upstream", "UNKNOWN")
|
| 78 |
if upstream == "OK":
|
| 79 |
+
return True, "Healthy · upstream OK"
|
| 80 |
+
return False, f"Gateway up, upstream unavailable ({upstream})"
|
| 81 |
except requests.RequestException as exc:
|
| 82 |
return False, str(exc)
|
| 83 |
|
app.py
CHANGED
|
@@ -193,7 +193,7 @@ RESULTS_SHELL_HTML = """
|
|
| 193 |
<div class="results-stack">
|
| 194 |
<textarea class="hiro-full-translation" readonly aria-hidden="true" tabindex="-1"></textarea>
|
| 195 |
<div class="progress-section">
|
| 196 |
-
<div class="progress-meta" id="hiro-progress-meta">
|
| 197 |
<div class="progress-track">
|
| 198 |
<div class="progress-fill" id="hiro-progress-fill" style="width:0%"></div>
|
| 199 |
</div>
|
|
@@ -202,31 +202,31 @@ RESULTS_SHELL_HTML = """
|
|
| 202 |
<div class="pairs-wrap" id="hiro-pairs-scroll">
|
| 203 |
<div id="hiro-pairs-header"></div>
|
| 204 |
<div id="hiro-pairs-body">
|
| 205 |
-
<div class="empty-hint">
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
"""
|
| 210 |
|
| 211 |
LANG_OPTIONS = [
|
| 212 |
-
("
|
| 213 |
-
("
|
| 214 |
-
("
|
| 215 |
-
("
|
| 216 |
-
("
|
| 217 |
-
("
|
| 218 |
-
("
|
| 219 |
-
("
|
| 220 |
-
("
|
| 221 |
-
("
|
| 222 |
-
("
|
| 223 |
-
("
|
| 224 |
-
("
|
| 225 |
-
("
|
| 226 |
-
("
|
| 227 |
-
("
|
| 228 |
-
("
|
| 229 |
-
("
|
| 230 |
]
|
| 231 |
|
| 232 |
EXAMPLES = [
|
|
@@ -266,24 +266,24 @@ def _has_health_endpoint(base: str) -> bool:
|
|
| 266 |
return "/compute/hiro_translation_api" in base
|
| 267 |
|
| 268 |
|
| 269 |
-
def check_service(
|
| 270 |
-
base = _resolve_base(
|
| 271 |
|
| 272 |
if not _has_health_endpoint(base):
|
| 273 |
return (
|
| 274 |
-
f'<span class="status-pill ok">●
|
| 275 |
-
f'<span style="opacity:0.85">
|
| 276 |
f"</span>"
|
| 277 |
)
|
| 278 |
|
| 279 |
ok, msg = health_ok(base, api_key=api_key or None)
|
| 280 |
if ok:
|
| 281 |
return (
|
| 282 |
-
f'<span class="status-pill ok">●
|
| 283 |
f'<span style="opacity:0.85">{html.escape(base)}</span></span>'
|
| 284 |
)
|
| 285 |
return (
|
| 286 |
-
f'<span class="status-pill err">●
|
| 287 |
f'<span style="opacity:0.85">{html.escape(msg)}</span></span>'
|
| 288 |
)
|
| 289 |
|
|
@@ -352,7 +352,7 @@ def _empty_outputs():
|
|
| 352 |
full="",
|
| 353 |
lang="zh2en",
|
| 354 |
percent=0,
|
| 355 |
-
status="
|
| 356 |
reset=True,
|
| 357 |
)
|
| 358 |
return _pack_outputs(stream_payload=payload, panel_visible=False, copy_enabled=False)
|
|
@@ -389,7 +389,7 @@ COPY_TRANSLATION_JS = """
|
|
| 389 |
const ta = document.querySelector(".hiro-full-translation");
|
| 390 |
const text = ta ? ta.value : "";
|
| 391 |
if (!String(text).trim()) {
|
| 392 |
-
alert("
|
| 393 |
return [];
|
| 394 |
}
|
| 395 |
const copyFallback = (value) => {
|
|
@@ -407,7 +407,7 @@ COPY_TRANSLATION_JS = """
|
|
| 407 |
if (!btn) return;
|
| 408 |
const label = btn.querySelector("button") || btn;
|
| 409 |
const orig = label.textContent;
|
| 410 |
-
label.textContent = "
|
| 411 |
setTimeout(() => { label.textContent = orig; }, 1500);
|
| 412 |
};
|
| 413 |
if (navigator.clipboard && window.isSecureContext) {
|
|
@@ -451,7 +451,7 @@ UPDATE_RESULTS_JS = """
|
|
| 451 |
const tgtCode = (parts[1] || "en").toLowerCase();
|
| 452 |
const src = codeMap[srcCode] || srcCode.toUpperCase();
|
| 453 |
const tgt = codeMap[tgtCode] || tgtCode.toUpperCase();
|
| 454 |
-
return { src: `
|
| 455 |
};
|
| 456 |
|
| 457 |
let state;
|
|
@@ -502,7 +502,7 @@ UPDATE_RESULTS_JS = """
|
|
| 502 |
|
| 503 |
if (totalVisible === 0 && store.count === 0) {
|
| 504 |
if (!body.querySelector(".empty-hint")) {
|
| 505 |
-
body.innerHTML = '<div class="empty-hint">
|
| 506 |
}
|
| 507 |
} else {
|
| 508 |
const hint = body.querySelector(".empty-hint");
|
|
@@ -519,7 +519,7 @@ UPDATE_RESULTS_JS = """
|
|
| 519 |
body.insertAdjacentHTML(
|
| 520 |
"beforeend",
|
| 521 |
`<div class="pair-block" data-idx="${displayIdx}">
|
| 522 |
-
<div class="pair-idx">
|
| 523 |
<div class="pair-row">
|
| 524 |
<div class="pair-cell src">${esc(orig)}</div>
|
| 525 |
<div class="pair-cell tgt">${esc(trans)}</div>
|
|
@@ -539,8 +539,8 @@ UPDATE_RESULTS_JS = """
|
|
| 539 |
"""
|
| 540 |
|
| 541 |
INIT_STREAM_WATCH_JS = """
|
| 542 |
-
(
|
| 543 |
-
if (window.__hiroStreamWatch) return [
|
| 544 |
window.__hiroStreamWatch = true;
|
| 545 |
|
| 546 |
const bind = () => {
|
|
@@ -564,18 +564,18 @@ INIT_STREAM_WATCH_JS = """
|
|
| 564 |
});
|
| 565 |
boot.observe(document.body, { childList: true, subtree: true });
|
| 566 |
}
|
| 567 |
-
return [
|
| 568 |
}
|
| 569 |
"""
|
| 570 |
|
| 571 |
RESET_STREAM_JS = """
|
| 572 |
-
(text, lang, mode,
|
| 573 |
if (window.__hiroResults) {
|
| 574 |
window.__hiroResults.count = 0;
|
| 575 |
window.__hiroResults.layout = null;
|
| 576 |
window.__hiroResults.lastJson = "";
|
| 577 |
}
|
| 578 |
-
return [text, lang, mode,
|
| 579 |
}
|
| 580 |
"""
|
| 581 |
|
|
@@ -591,13 +591,13 @@ CLEAR_STREAM_JS = """
|
|
| 591 |
"""
|
| 592 |
|
| 593 |
|
| 594 |
-
def run_translation(text: str, lang: str, mode: str,
|
| 595 |
text = (text or "").strip()
|
| 596 |
if not text:
|
| 597 |
yield _empty_outputs()
|
| 598 |
return
|
| 599 |
|
| 600 |
-
base = _resolve_base(
|
| 601 |
auth = api_key or None
|
| 602 |
pairs: list[tuple[str, str]] = []
|
| 603 |
t0 = time.perf_counter()
|
|
@@ -621,7 +621,7 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 621 |
reset=reset,
|
| 622 |
)
|
| 623 |
|
| 624 |
-
yield emit(percent=0, status="
|
| 625 |
|
| 626 |
try:
|
| 627 |
if mode == "fast":
|
|
@@ -630,7 +630,7 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 630 |
trans = data.get("text_translated", "")
|
| 631 |
elapsed = time.perf_counter() - t0
|
| 632 |
pairs = [(orig, trans)]
|
| 633 |
-
status = f"
|
| 634 |
out_chars = data.get("translated_character_count")
|
| 635 |
if out_chars is not None:
|
| 636 |
status += f" · {out_chars} chars"
|
|
@@ -646,7 +646,11 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 646 |
last_progress = chunk.get("progress") or last_progress
|
| 647 |
pct = _progress_percent(last_progress, len(pairs))
|
| 648 |
total = _parse_progress(last_progress)[1]
|
| 649 |
-
desc =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
elapsed = time.perf_counter() - t0
|
| 651 |
|
| 652 |
visible = _pairs_for_display(pairs)
|
|
@@ -658,7 +662,7 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 658 |
):
|
| 659 |
out = emit(
|
| 660 |
percent=pct,
|
| 661 |
-
status=f"
|
| 662 |
)
|
| 663 |
if out is not None:
|
| 664 |
yield out
|
|
@@ -666,7 +670,9 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 666 |
elapsed = time.perf_counter() - t0
|
| 667 |
yield emit(
|
| 668 |
percent=100,
|
| 669 |
-
status=
|
|
|
|
|
|
|
| 670 |
force=True,
|
| 671 |
)
|
| 672 |
|
|
@@ -678,7 +684,7 @@ def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str
|
|
| 678 |
full="",
|
| 679 |
lang=lang,
|
| 680 |
percent=0,
|
| 681 |
-
status=f"
|
| 682 |
reset=True,
|
| 683 |
)
|
| 684 |
yield _pack_outputs(stream_payload=payload, panel_visible=True, copy_enabled=False)
|
|
@@ -703,44 +709,40 @@ def build_ui() -> gr.Blocks:
|
|
| 703 |
) as demo:
|
| 704 |
with gr.Sidebar():
|
| 705 |
gr.HTML('<p class="main-title">HIRO Translation</p>')
|
| 706 |
-
gr.Markdown(
|
|
|
|
|
|
|
| 707 |
|
| 708 |
gateway_status = gr.HTML(value=check_service(DEFAULT_BASE))
|
| 709 |
-
refresh_btn = gr.Button("
|
| 710 |
|
| 711 |
with gr.Row():
|
| 712 |
source_lang = gr.Dropdown(
|
| 713 |
choices=LANG_OPTIONS,
|
| 714 |
value="zh",
|
| 715 |
-
label="
|
| 716 |
)
|
| 717 |
target_lang = gr.Dropdown(
|
| 718 |
choices=LANG_OPTIONS,
|
| 719 |
value="en",
|
| 720 |
-
label="
|
| 721 |
)
|
| 722 |
lang = gr.Textbox(value="zh2en", visible=False)
|
| 723 |
mode = gr.Radio(
|
| 724 |
choices=["stream", "fast"],
|
| 725 |
value="stream",
|
| 726 |
-
label="
|
| 727 |
-
info="stream
|
| 728 |
)
|
| 729 |
|
| 730 |
-
with gr.Accordion("
|
| 731 |
-
api_base = gr.Textbox(
|
| 732 |
-
label="API Base URL",
|
| 733 |
-
value=DEFAULT_BASE,
|
| 734 |
-
placeholder=DEFAULT_BASE,
|
| 735 |
-
info="公开网关默认 https://connect.zhihuiya.com/hiro_translation;内网可填 stage 地址",
|
| 736 |
-
)
|
| 737 |
api_key = gr.Textbox(
|
| 738 |
label="API Key (Bearer)",
|
| 739 |
type="password",
|
| 740 |
-
placeholder="sk-...
|
| 741 |
)
|
| 742 |
|
| 743 |
-
gr.Markdown("**
|
| 744 |
example_btns: list[tuple[gr.Button, str, str, str]] = []
|
| 745 |
for i, (sample, src, tgt) in enumerate(EXAMPLES, start=1):
|
| 746 |
preview = sample.replace("\n", " ")[:36]
|
|
@@ -751,16 +753,19 @@ def build_ui() -> gr.Blocks:
|
|
| 751 |
|
| 752 |
with gr.Column():
|
| 753 |
input_text = gr.Textbox(
|
| 754 |
-
label="
|
| 755 |
-
placeholder=
|
|
|
|
|
|
|
|
|
|
| 756 |
lines=8,
|
| 757 |
elem_id="input-box",
|
| 758 |
)
|
| 759 |
with gr.Row(elem_classes=["toolbar-row"]):
|
| 760 |
-
submit_btn = gr.Button("
|
| 761 |
-
clear_btn = gr.Button("
|
| 762 |
copy_btn = gr.Button(
|
| 763 |
-
"
|
| 764 |
variant="secondary",
|
| 765 |
interactive=False,
|
| 766 |
scale=0,
|
|
@@ -775,15 +780,14 @@ def build_ui() -> gr.Blocks:
|
|
| 775 |
full="",
|
| 776 |
lang="zh2en",
|
| 777 |
percent=0,
|
| 778 |
-
status="
|
| 779 |
),
|
| 780 |
visible=False,
|
| 781 |
elem_id="hiro-stream-state",
|
| 782 |
)
|
| 783 |
|
| 784 |
-
refresh_btn.click(check_service, inputs=[
|
| 785 |
-
|
| 786 |
-
api_key.change(check_service, inputs=[api_base, api_key], outputs=[gateway_status])
|
| 787 |
|
| 788 |
def _combine_lang(src: str, tgt: str) -> str:
|
| 789 |
return f"{src}2{tgt}"
|
|
@@ -803,7 +807,7 @@ def build_ui() -> gr.Blocks:
|
|
| 803 |
|
| 804 |
submit_btn.click(
|
| 805 |
fn=run_translation,
|
| 806 |
-
inputs=[input_text, lang, mode,
|
| 807 |
outputs=[stream_state, results_panel, copy_btn],
|
| 808 |
js=RESET_STREAM_JS,
|
| 809 |
)
|
|
@@ -830,7 +834,7 @@ def build_ui() -> gr.Blocks:
|
|
| 830 |
full="",
|
| 831 |
lang="zh2en",
|
| 832 |
percent=0,
|
| 833 |
-
status="
|
| 834 |
reset=True,
|
| 835 |
)
|
| 836 |
return "", *_pack_outputs(
|
|
@@ -845,7 +849,7 @@ def build_ui() -> gr.Blocks:
|
|
| 845 |
|
| 846 |
demo.load(
|
| 847 |
check_service,
|
| 848 |
-
inputs=[
|
| 849 |
outputs=[gateway_status],
|
| 850 |
js=INIT_STREAM_WATCH_JS,
|
| 851 |
)
|
|
|
|
| 193 |
<div class="results-stack">
|
| 194 |
<textarea class="hiro-full-translation" readonly aria-hidden="true" tabindex="-1"></textarea>
|
| 195 |
<div class="progress-section">
|
| 196 |
+
<div class="progress-meta" id="hiro-progress-meta">Ready</div>
|
| 197 |
<div class="progress-track">
|
| 198 |
<div class="progress-fill" id="hiro-progress-fill" style="width:0%"></div>
|
| 199 |
</div>
|
|
|
|
| 202 |
<div class="pairs-wrap" id="hiro-pairs-scroll">
|
| 203 |
<div id="hiro-pairs-header"></div>
|
| 204 |
<div id="hiro-pairs-body">
|
| 205 |
+
<div class="empty-hint">Translation results will appear here</div>
|
| 206 |
</div>
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
"""
|
| 210 |
|
| 211 |
LANG_OPTIONS = [
|
| 212 |
+
("Chinese", "zh"),
|
| 213 |
+
("English", "en"),
|
| 214 |
+
("Japanese", "ja"),
|
| 215 |
+
("Korean", "ko"),
|
| 216 |
+
("German", "de"),
|
| 217 |
+
("French", "fr"),
|
| 218 |
+
("Russian", "ru"),
|
| 219 |
+
("Spanish", "es"),
|
| 220 |
+
("Portuguese", "pt"),
|
| 221 |
+
("Italian", "it"),
|
| 222 |
+
("Dutch", "nl"),
|
| 223 |
+
("Arabic", "ar"),
|
| 224 |
+
("Hindi", "hi"),
|
| 225 |
+
("Thai", "th"),
|
| 226 |
+
("Vietnamese", "vi"),
|
| 227 |
+
("Indonesian", "id"),
|
| 228 |
+
("Turkish", "tr"),
|
| 229 |
+
("Polish", "pl"),
|
| 230 |
]
|
| 231 |
|
| 232 |
EXAMPLES = [
|
|
|
|
| 266 |
return "/compute/hiro_translation_api" in base
|
| 267 |
|
| 268 |
|
| 269 |
+
def check_service(api_key: str = "") -> str:
|
| 270 |
+
base = _resolve_base(DEFAULT_BASE)
|
| 271 |
|
| 272 |
if not _has_health_endpoint(base):
|
| 273 |
return (
|
| 274 |
+
f'<span class="status-pill ok">● Public gateway<br>'
|
| 275 |
+
f'<span style="opacity:0.85">Bearer auth on request · {html.escape(base)}</span>'
|
| 276 |
f"</span>"
|
| 277 |
)
|
| 278 |
|
| 279 |
ok, msg = health_ok(base, api_key=api_key or None)
|
| 280 |
if ok:
|
| 281 |
return (
|
| 282 |
+
f'<span class="status-pill ok">● Online<br>'
|
| 283 |
f'<span style="opacity:0.85">{html.escape(base)}</span></span>'
|
| 284 |
)
|
| 285 |
return (
|
| 286 |
+
f'<span class="status-pill err">● Unavailable<br>'
|
| 287 |
f'<span style="opacity:0.85">{html.escape(msg)}</span></span>'
|
| 288 |
)
|
| 289 |
|
|
|
|
| 352 |
full="",
|
| 353 |
lang="zh2en",
|
| 354 |
percent=0,
|
| 355 |
+
status="Waiting for input…",
|
| 356 |
reset=True,
|
| 357 |
)
|
| 358 |
return _pack_outputs(stream_payload=payload, panel_visible=False, copy_enabled=False)
|
|
|
|
| 389 |
const ta = document.querySelector(".hiro-full-translation");
|
| 390 |
const text = ta ? ta.value : "";
|
| 391 |
if (!String(text).trim()) {
|
| 392 |
+
alert("Nothing to copy yet");
|
| 393 |
return [];
|
| 394 |
}
|
| 395 |
const copyFallback = (value) => {
|
|
|
|
| 407 |
if (!btn) return;
|
| 408 |
const label = btn.querySelector("button") || btn;
|
| 409 |
const orig = label.textContent;
|
| 410 |
+
label.textContent = "Copied";
|
| 411 |
setTimeout(() => { label.textContent = orig; }, 1500);
|
| 412 |
};
|
| 413 |
if (navigator.clipboard && window.isSecureContext) {
|
|
|
|
| 451 |
const tgtCode = (parts[1] || "en").toLowerCase();
|
| 452 |
const src = codeMap[srcCode] || srcCode.toUpperCase();
|
| 453 |
const tgt = codeMap[tgtCode] || tgtCode.toUpperCase();
|
| 454 |
+
return { src: `Source (${src})`, tgt: `Target (${tgt})` };
|
| 455 |
};
|
| 456 |
|
| 457 |
let state;
|
|
|
|
| 502 |
|
| 503 |
if (totalVisible === 0 && store.count === 0) {
|
| 504 |
if (!body.querySelector(".empty-hint")) {
|
| 505 |
+
body.innerHTML = '<div class="empty-hint">Translation results will appear here</div>';
|
| 506 |
}
|
| 507 |
} else {
|
| 508 |
const hint = body.querySelector(".empty-hint");
|
|
|
|
| 519 |
body.insertAdjacentHTML(
|
| 520 |
"beforeend",
|
| 521 |
`<div class="pair-block" data-idx="${displayIdx}">
|
| 522 |
+
<div class="pair-idx">Segment ${displayIdx}</div>
|
| 523 |
<div class="pair-row">
|
| 524 |
<div class="pair-cell src">${esc(orig)}</div>
|
| 525 |
<div class="pair-cell tgt">${esc(trans)}</div>
|
|
|
|
| 539 |
"""
|
| 540 |
|
| 541 |
INIT_STREAM_WATCH_JS = """
|
| 542 |
+
() => {
|
| 543 |
+
if (window.__hiroStreamWatch) return [];
|
| 544 |
window.__hiroStreamWatch = true;
|
| 545 |
|
| 546 |
const bind = () => {
|
|
|
|
| 564 |
});
|
| 565 |
boot.observe(document.body, { childList: true, subtree: true });
|
| 566 |
}
|
| 567 |
+
return [];
|
| 568 |
}
|
| 569 |
"""
|
| 570 |
|
| 571 |
RESET_STREAM_JS = """
|
| 572 |
+
(text, lang, mode, api_key) => {
|
| 573 |
if (window.__hiroResults) {
|
| 574 |
window.__hiroResults.count = 0;
|
| 575 |
window.__hiroResults.layout = null;
|
| 576 |
window.__hiroResults.lastJson = "";
|
| 577 |
}
|
| 578 |
+
return [text, lang, mode, api_key];
|
| 579 |
}
|
| 580 |
"""
|
| 581 |
|
|
|
|
| 591 |
"""
|
| 592 |
|
| 593 |
|
| 594 |
+
def run_translation(text: str, lang: str, mode: str, api_key: str = ""):
|
| 595 |
text = (text or "").strip()
|
| 596 |
if not text:
|
| 597 |
yield _empty_outputs()
|
| 598 |
return
|
| 599 |
|
| 600 |
+
base = _resolve_base(DEFAULT_BASE)
|
| 601 |
auth = api_key or None
|
| 602 |
pairs: list[tuple[str, str]] = []
|
| 603 |
t0 = time.perf_counter()
|
|
|
|
| 621 |
reset=reset,
|
| 622 |
)
|
| 623 |
|
| 624 |
+
yield emit(percent=0, status="Connecting to gateway…", reset=True)
|
| 625 |
|
| 626 |
try:
|
| 627 |
if mode == "fast":
|
|
|
|
| 630 |
trans = data.get("text_translated", "")
|
| 631 |
elapsed = time.perf_counter() - t0
|
| 632 |
pairs = [(orig, trans)]
|
| 633 |
+
status = f"Done · fast mode · {elapsed:.1f}s"
|
| 634 |
out_chars = data.get("translated_character_count")
|
| 635 |
if out_chars is not None:
|
| 636 |
status += f" · {out_chars} chars"
|
|
|
|
| 646 |
last_progress = chunk.get("progress") or last_progress
|
| 647 |
pct = _progress_percent(last_progress, len(pairs))
|
| 648 |
total = _parse_progress(last_progress)[1]
|
| 649 |
+
desc = (
|
| 650 |
+
f"Translating {last_progress}"
|
| 651 |
+
if total
|
| 652 |
+
else f"Received {len(pairs)} segment(s)"
|
| 653 |
+
)
|
| 654 |
elapsed = time.perf_counter() - t0
|
| 655 |
|
| 656 |
visible = _pairs_for_display(pairs)
|
|
|
|
| 662 |
):
|
| 663 |
out = emit(
|
| 664 |
percent=pct,
|
| 665 |
+
status=f"Streaming · {desc} · {elapsed:.1f}s",
|
| 666 |
)
|
| 667 |
if out is not None:
|
| 668 |
yield out
|
|
|
|
| 670 |
elapsed = time.perf_counter() - t0
|
| 671 |
yield emit(
|
| 672 |
percent=100,
|
| 673 |
+
status=(
|
| 674 |
+
f"Done · {len(pairs)} segment(s) · {elapsed:.1f}s · {last_progress}"
|
| 675 |
+
),
|
| 676 |
force=True,
|
| 677 |
)
|
| 678 |
|
|
|
|
| 684 |
full="",
|
| 685 |
lang=lang,
|
| 686 |
percent=0,
|
| 687 |
+
status=f"Failed · {elapsed:.1f}s · {err}",
|
| 688 |
reset=True,
|
| 689 |
)
|
| 690 |
yield _pack_outputs(stream_payload=payload, panel_visible=True, copy_enabled=False)
|
|
|
|
| 709 |
) as demo:
|
| 710 |
with gr.Sidebar():
|
| 711 |
gr.HTML('<p class="main-title">HIRO Translation</p>')
|
| 712 |
+
gr.Markdown(
|
| 713 |
+
"Patent translation · `connect.zhihuiya.com` / `hiro-translation-api`"
|
| 714 |
+
)
|
| 715 |
|
| 716 |
gateway_status = gr.HTML(value=check_service(DEFAULT_BASE))
|
| 717 |
+
refresh_btn = gr.Button("Check gateway", size="sm", variant="secondary")
|
| 718 |
|
| 719 |
with gr.Row():
|
| 720 |
source_lang = gr.Dropdown(
|
| 721 |
choices=LANG_OPTIONS,
|
| 722 |
value="zh",
|
| 723 |
+
label="Source language",
|
| 724 |
)
|
| 725 |
target_lang = gr.Dropdown(
|
| 726 |
choices=LANG_OPTIONS,
|
| 727 |
value="en",
|
| 728 |
+
label="Target language",
|
| 729 |
)
|
| 730 |
lang = gr.Textbox(value="zh2en", visible=False)
|
| 731 |
mode = gr.Radio(
|
| 732 |
choices=["stream", "fast"],
|
| 733 |
value="stream",
|
| 734 |
+
label="Mode",
|
| 735 |
+
info="stream: segment streaming; fast: single full response",
|
| 736 |
)
|
| 737 |
|
| 738 |
+
with gr.Accordion("Advanced", open=False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 739 |
api_key = gr.Textbox(
|
| 740 |
label="API Key (Bearer)",
|
| 741 |
type="password",
|
| 742 |
+
placeholder="sk-... or set HIRO_API_KEY",
|
| 743 |
)
|
| 744 |
|
| 745 |
+
gr.Markdown("**Examples**")
|
| 746 |
example_btns: list[tuple[gr.Button, str, str, str]] = []
|
| 747 |
for i, (sample, src, tgt) in enumerate(EXAMPLES, start=1):
|
| 748 |
preview = sample.replace("\n", " ")[:36]
|
|
|
|
| 753 |
|
| 754 |
with gr.Column():
|
| 755 |
input_text = gr.Textbox(
|
| 756 |
+
label="Source text",
|
| 757 |
+
placeholder=(
|
| 758 |
+
"Enter patent-related text. Supports Chinese, English, "
|
| 759 |
+
"Japanese, Korean, and more."
|
| 760 |
+
),
|
| 761 |
lines=8,
|
| 762 |
elem_id="input-box",
|
| 763 |
)
|
| 764 |
with gr.Row(elem_classes=["toolbar-row"]):
|
| 765 |
+
submit_btn = gr.Button("Translate", variant="primary", scale=0)
|
| 766 |
+
clear_btn = gr.Button("Clear", scale=0)
|
| 767 |
copy_btn = gr.Button(
|
| 768 |
+
"Copy full translation",
|
| 769 |
variant="secondary",
|
| 770 |
interactive=False,
|
| 771 |
scale=0,
|
|
|
|
| 780 |
full="",
|
| 781 |
lang="zh2en",
|
| 782 |
percent=0,
|
| 783 |
+
status="Ready",
|
| 784 |
),
|
| 785 |
visible=False,
|
| 786 |
elem_id="hiro-stream-state",
|
| 787 |
)
|
| 788 |
|
| 789 |
+
refresh_btn.click(check_service, inputs=[api_key], outputs=[gateway_status])
|
| 790 |
+
api_key.change(check_service, inputs=[api_key], outputs=[gateway_status])
|
|
|
|
| 791 |
|
| 792 |
def _combine_lang(src: str, tgt: str) -> str:
|
| 793 |
return f"{src}2{tgt}"
|
|
|
|
| 807 |
|
| 808 |
submit_btn.click(
|
| 809 |
fn=run_translation,
|
| 810 |
+
inputs=[input_text, lang, mode, api_key],
|
| 811 |
outputs=[stream_state, results_panel, copy_btn],
|
| 812 |
js=RESET_STREAM_JS,
|
| 813 |
)
|
|
|
|
| 834 |
full="",
|
| 835 |
lang="zh2en",
|
| 836 |
percent=0,
|
| 837 |
+
status="Waiting for input…",
|
| 838 |
reset=True,
|
| 839 |
)
|
| 840 |
return "", *_pack_outputs(
|
|
|
|
| 849 |
|
| 850 |
demo.load(
|
| 851 |
check_service,
|
| 852 |
+
inputs=[api_key],
|
| 853 |
outputs=[gateway_status],
|
| 854 |
js=INIT_STREAM_WATCH_JS,
|
| 855 |
)
|