Spaces:
Running
Running
Jiawei Dong commited on
Commit ·
067d7b2
1
Parent(s): 4a9a269
v2
Browse files- README.md +22 -5
- __pycache__/api_client.cpython-310.pyc +0 -0
- __pycache__/api_client.cpython-38.pyc +0 -0
- __pycache__/app.cpython-38.pyc +0 -0
- api_client.py +84 -12
- app.py +50 -22
README.md
CHANGED
|
@@ -14,24 +14,41 @@ short_description: Patent translation demo via hiro-translation-api
|
|
| 14 |
|
| 15 |
# HIRO Translation
|
| 16 |
|
| 17 |
-
Patent translation demo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
-
|
| 20 |
-
- **fast** — single JSON response with full translation
|
| 21 |
-
- Default backend: `stage-s-patsnaprd-hiro-translation-api-internet.patsnap.info`
|
| 22 |
|
| 23 |
## Files
|
| 24 |
|
| 25 |
| File | Purpose |
|
| 26 |
|------|---------|
|
| 27 |
| `app.py` | Gradio UI |
|
| 28 |
-
| `api_client.py` | HTTP client for `/
|
| 29 |
| `requirements.txt` | Python dependencies (`requests`; Gradio is pre-installed on Spaces) |
|
| 30 |
|
| 31 |
## Local run
|
| 32 |
|
| 33 |
```bash
|
| 34 |
pip install -r requirements.txt
|
|
|
|
| 35 |
python app.py
|
| 36 |
```
|
| 37 |
|
|
|
|
| 14 |
|
| 15 |
# HIRO Translation
|
| 16 |
|
| 17 |
+
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 |
+
- Default gateway: `https://connect.zhihuiya.com/hiro_translation`
|
| 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 |
|
| 42 |
|------|---------|
|
| 43 |
| `app.py` | Gradio UI |
|
| 44 |
+
| `api_client.py` | HTTP client for `/translate` (camelCase request/response) |
|
| 45 |
| `requirements.txt` | Python dependencies (`requests`; Gradio is pre-installed on Spaces) |
|
| 46 |
|
| 47 |
## Local run
|
| 48 |
|
| 49 |
```bash
|
| 50 |
pip install -r requirements.txt
|
| 51 |
+
export HIRO_API_KEY="sk-..." # optional if set in UI
|
| 52 |
python app.py
|
| 53 |
```
|
| 54 |
|
__pycache__/api_client.cpython-310.pyc
ADDED
|
Binary file (4.78 kB). View file
|
|
|
__pycache__/api_client.cpython-38.pyc
CHANGED
|
Binary files a/__pycache__/api_client.cpython-38.pyc and b/__pycache__/api_client.cpython-38.pyc differ
|
|
|
__pycache__/app.cpython-38.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-38.pyc and b/__pycache__/app.cpython-38.pyc differ
|
|
|
api_client.py
CHANGED
|
@@ -1,26 +1,75 @@
|
|
| 1 |
-
"""HTTP client for
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import json
|
|
|
|
| 6 |
from collections.abc import Iterator
|
| 7 |
from typing import Any
|
| 8 |
|
| 9 |
import requests
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
def _api_url(base_url: str, path: str) -> str:
|
| 18 |
-
return f"{base_url.rstrip('/')}{
|
| 19 |
|
| 20 |
|
| 21 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
try:
|
| 23 |
-
r = requests.get(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
r.raise_for_status()
|
| 25 |
data = r.json()
|
| 26 |
if data.get("status") != "OK":
|
|
@@ -38,11 +87,13 @@ def translate_fast(
|
|
| 38 |
lang: str,
|
| 39 |
*,
|
| 40 |
base_url: str = DEFAULT_BASE,
|
|
|
|
| 41 |
timeout: int = 1800,
|
| 42 |
) -> dict[str, Any]:
|
| 43 |
r = requests.post(
|
| 44 |
_api_url(base_url, "/translate"),
|
| 45 |
-
json=
|
|
|
|
| 46 |
timeout=timeout,
|
| 47 |
)
|
| 48 |
if r.status_code >= 400:
|
|
@@ -51,7 +102,18 @@ def translate_fast(
|
|
| 51 |
except json.JSONDecodeError:
|
| 52 |
err = r.text[:500]
|
| 53 |
raise RuntimeError(err)
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
|
| 57 |
def _iter_sse_json(resp: requests.Response) -> Iterator[dict[str, Any]]:
|
|
@@ -67,7 +129,15 @@ def _iter_sse_json(resp: requests.Response) -> Iterator[dict[str, Any]]:
|
|
| 67 |
chunk = json.loads(payload)
|
| 68 |
if isinstance(chunk, dict) and chunk.get("error"):
|
| 69 |
raise RuntimeError(str(chunk["error"]))
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
|
| 73 |
def stream_translate(
|
|
@@ -75,11 +145,13 @@ def stream_translate(
|
|
| 75 |
lang: str,
|
| 76 |
*,
|
| 77 |
base_url: str = DEFAULT_BASE,
|
|
|
|
| 78 |
timeout: int = 1800,
|
| 79 |
) -> Iterator[dict[str, Any]]:
|
| 80 |
with requests.post(
|
| 81 |
_api_url(base_url, "/translate"),
|
| 82 |
-
json=
|
|
|
|
| 83 |
stream=True,
|
| 84 |
timeout=timeout,
|
| 85 |
) as resp:
|
|
|
|
| 1 |
+
"""HTTP client for HIRO Translation API (public connect gateway or internal stage)."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import json
|
| 6 |
+
import os
|
| 7 |
from collections.abc import Iterator
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
import requests
|
| 11 |
|
| 12 |
+
# Public gateway (matches curl examples against connect.zhihuiya.com).
|
| 13 |
+
DEFAULT_BASE = "https://connect.zhihuiya.com/hiro_translation"
|
| 14 |
+
|
| 15 |
+
API_KEY_ENV = "HIRO_API_KEY"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def lang_to_codes(lang: str) -> tuple[str, str]:
|
| 19 |
+
src, _, tgt = lang.partition("2")
|
| 20 |
+
if not src or not tgt:
|
| 21 |
+
raise ValueError(f"invalid lang {lang!r}; expected format like zh2en")
|
| 22 |
+
return src, tgt
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def translate_payload(
|
| 26 |
+
content: str,
|
| 27 |
+
lang: str,
|
| 28 |
+
*,
|
| 29 |
+
mode: str,
|
| 30 |
+
) -> dict[str, Any]:
|
| 31 |
+
source, target = lang_to_codes(lang)
|
| 32 |
+
return {
|
| 33 |
+
"content": content,
|
| 34 |
+
"sourceLanguageCode": source,
|
| 35 |
+
"targetLanguageCode": target,
|
| 36 |
+
"mode": mode,
|
| 37 |
+
}
|
| 38 |
|
| 39 |
|
| 40 |
def _api_url(base_url: str, path: str) -> str:
|
| 41 |
+
return f"{base_url.rstrip('/')}{path}"
|
| 42 |
|
| 43 |
|
| 44 |
+
def _request_headers(api_key: str | None = None) -> dict[str, str]:
|
| 45 |
+
headers = {"Content-Type": "application/json"}
|
| 46 |
+
key = (api_key if api_key is not None else os.environ.get(API_KEY_ENV, "")).strip()
|
| 47 |
+
if key:
|
| 48 |
+
headers["Authorization"] = f"Bearer {key}"
|
| 49 |
+
return headers
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _translated_text(data: dict[str, Any]) -> str:
|
| 53 |
+
value = data.get("textTranslated", data.get("text_translated", ""))
|
| 54 |
+
return value if isinstance(value, str) else ""
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def _original_text(data: dict[str, Any], fallback: str = "") -> str:
|
| 58 |
+
value = data.get("textOriginal", data.get("text_original", fallback))
|
| 59 |
+
return value if isinstance(value, str) else fallback
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def health_ok(
|
| 63 |
+
base_url: str = DEFAULT_BASE,
|
| 64 |
+
*,
|
| 65 |
+
api_key: str | None = None,
|
| 66 |
+
) -> tuple[bool, str]:
|
| 67 |
try:
|
| 68 |
+
r = requests.get(
|
| 69 |
+
_api_url(base_url, "/health"),
|
| 70 |
+
headers=_request_headers(api_key),
|
| 71 |
+
timeout=12,
|
| 72 |
+
)
|
| 73 |
r.raise_for_status()
|
| 74 |
data = r.json()
|
| 75 |
if data.get("status") != "OK":
|
|
|
|
| 87 |
lang: str,
|
| 88 |
*,
|
| 89 |
base_url: str = DEFAULT_BASE,
|
| 90 |
+
api_key: str | None = None,
|
| 91 |
timeout: int = 1800,
|
| 92 |
) -> dict[str, Any]:
|
| 93 |
r = requests.post(
|
| 94 |
_api_url(base_url, "/translate"),
|
| 95 |
+
json=translate_payload(text, lang, mode="fast"),
|
| 96 |
+
headers=_request_headers(api_key),
|
| 97 |
timeout=timeout,
|
| 98 |
)
|
| 99 |
if r.status_code >= 400:
|
|
|
|
| 102 |
except json.JSONDecodeError:
|
| 103 |
err = r.text[:500]
|
| 104 |
raise RuntimeError(err)
|
| 105 |
+
data = r.json()
|
| 106 |
+
if data.get("state") not in (None, "success") and "error" in data:
|
| 107 |
+
raise RuntimeError(str(data.get("error", data)))
|
| 108 |
+
# Normalize to keys the UI already understands (snake_case aliases).
|
| 109 |
+
return {
|
| 110 |
+
"state": data.get("state", "success"),
|
| 111 |
+
"text_original": _original_text(data, text),
|
| 112 |
+
"text_translated": _translated_text(data),
|
| 113 |
+
"translated_character_count": data.get("translatedCharacterCount"),
|
| 114 |
+
"billing_amount": r.headers.get("X-Openapi-Amount"),
|
| 115 |
+
"raw": data,
|
| 116 |
+
}
|
| 117 |
|
| 118 |
|
| 119 |
def _iter_sse_json(resp: requests.Response) -> Iterator[dict[str, Any]]:
|
|
|
|
| 129 |
chunk = json.loads(payload)
|
| 130 |
if isinstance(chunk, dict) and chunk.get("error"):
|
| 131 |
raise RuntimeError(str(chunk["error"]))
|
| 132 |
+
if isinstance(chunk, dict):
|
| 133 |
+
yield {
|
| 134 |
+
"state": chunk.get("state", "success"),
|
| 135 |
+
"text_original": _original_text(chunk),
|
| 136 |
+
"text_translated": _translated_text(chunk),
|
| 137 |
+
"translated_character_count": chunk.get("translatedCharacterCount"),
|
| 138 |
+
"progress": chunk.get("progress"),
|
| 139 |
+
"raw": chunk,
|
| 140 |
+
}
|
| 141 |
|
| 142 |
|
| 143 |
def stream_translate(
|
|
|
|
| 145 |
lang: str,
|
| 146 |
*,
|
| 147 |
base_url: str = DEFAULT_BASE,
|
| 148 |
+
api_key: str | None = None,
|
| 149 |
timeout: int = 1800,
|
| 150 |
) -> Iterator[dict[str, Any]]:
|
| 151 |
with requests.post(
|
| 152 |
_api_url(base_url, "/translate"),
|
| 153 |
+
json=translate_payload(text, lang, mode="stream"),
|
| 154 |
+
headers=_request_headers(api_key),
|
| 155 |
stream=True,
|
| 156 |
timeout=timeout,
|
| 157 |
) as resp:
|
app.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
"""
|
| 2 |
-
Gradio demo — HIRO Translation API
|
| 3 |
|
| 4 |
-
Calls
|
| 5 |
-
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
from __future__ import annotations
|
|
@@ -228,19 +230,19 @@ LANG_OPTIONS = [
|
|
| 228 |
]
|
| 229 |
|
| 230 |
EXAMPLES = [
|
| 231 |
-
["一种
|
| 232 |
[
|
| 233 |
-
"
|
| 234 |
-
"en",
|
| 235 |
"zh",
|
|
|
|
| 236 |
],
|
| 237 |
[
|
| 238 |
-
"
|
| 239 |
-
"zh",
|
| 240 |
"en",
|
|
|
|
| 241 |
],
|
| 242 |
[
|
| 243 |
-
"
|
| 244 |
"zh",
|
| 245 |
"en",
|
| 246 |
],
|
|
@@ -254,9 +256,27 @@ def _resolve_base(api_base: str) -> str:
|
|
| 254 |
return (api_base or DEFAULT_BASE).strip().rstrip("/")
|
| 255 |
|
| 256 |
|
| 257 |
-
def
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
base = _resolve_base(api_base)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
if ok:
|
| 261 |
return (
|
| 262 |
f'<span class="status-pill ok">● 在线<br>'
|
|
@@ -571,13 +591,14 @@ CLEAR_STREAM_JS = """
|
|
| 571 |
"""
|
| 572 |
|
| 573 |
|
| 574 |
-
def run_translation(text: str, lang: str, mode: str, api_base: str):
|
| 575 |
text = (text or "").strip()
|
| 576 |
if not text:
|
| 577 |
yield _empty_outputs()
|
| 578 |
return
|
| 579 |
|
| 580 |
base = _resolve_base(api_base)
|
|
|
|
| 581 |
pairs: list[tuple[str, str]] = []
|
| 582 |
t0 = time.perf_counter()
|
| 583 |
last_progress = "0/0"
|
|
@@ -604,19 +625,19 @@ def run_translation(text: str, lang: str, mode: str, api_base: str):
|
|
| 604 |
|
| 605 |
try:
|
| 606 |
if mode == "fast":
|
| 607 |
-
data = translate_fast(text, lang, base_url=base)
|
| 608 |
orig = data.get("text_original", text)
|
| 609 |
trans = data.get("text_translated", "")
|
| 610 |
-
engine = data.get("engine", "")
|
| 611 |
elapsed = time.perf_counter() - t0
|
| 612 |
pairs = [(orig, trans)]
|
| 613 |
status = f"完成 · fast 模式 · {elapsed:.1f}s"
|
| 614 |
-
|
| 615 |
-
|
|
|
|
| 616 |
yield emit(percent=100, status=status, force=True)
|
| 617 |
return
|
| 618 |
|
| 619 |
-
for chunk in stream_translate(text, lang, base_url=base):
|
| 620 |
orig = chunk.get("text_original") or ""
|
| 621 |
trans = chunk.get("text_translated") or ""
|
| 622 |
if orig or trans:
|
|
@@ -682,7 +703,7 @@ def build_ui() -> gr.Blocks:
|
|
| 682 |
) as demo:
|
| 683 |
with gr.Sidebar():
|
| 684 |
gr.HTML('<p class="main-title">HIRO Translation</p>')
|
| 685 |
-
gr.Markdown("专利翻译 · `hiro-translation-api`")
|
| 686 |
|
| 687 |
gateway_status = gr.HTML(value=check_service(DEFAULT_BASE))
|
| 688 |
refresh_btn = gr.Button("检查网关", size="sm", variant="secondary")
|
|
@@ -711,6 +732,12 @@ def build_ui() -> gr.Blocks:
|
|
| 711 |
label="API Base URL",
|
| 712 |
value=DEFAULT_BASE,
|
| 713 |
placeholder=DEFAULT_BASE,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
)
|
| 715 |
|
| 716 |
gr.Markdown("**快速示例**")
|
|
@@ -754,8 +781,9 @@ def build_ui() -> gr.Blocks:
|
|
| 754 |
elem_id="hiro-stream-state",
|
| 755 |
)
|
| 756 |
|
| 757 |
-
refresh_btn.click(check_service, inputs=[api_base], outputs=[gateway_status])
|
| 758 |
-
api_base.change(check_service, inputs=[api_base], outputs=[gateway_status])
|
|
|
|
| 759 |
|
| 760 |
def _combine_lang(src: str, tgt: str) -> str:
|
| 761 |
return f"{src}2{tgt}"
|
|
@@ -775,7 +803,7 @@ def build_ui() -> gr.Blocks:
|
|
| 775 |
|
| 776 |
submit_btn.click(
|
| 777 |
fn=run_translation,
|
| 778 |
-
inputs=[input_text, lang, mode, api_base],
|
| 779 |
outputs=[stream_state, results_panel, copy_btn],
|
| 780 |
js=RESET_STREAM_JS,
|
| 781 |
)
|
|
@@ -817,7 +845,7 @@ def build_ui() -> gr.Blocks:
|
|
| 817 |
|
| 818 |
demo.load(
|
| 819 |
check_service,
|
| 820 |
-
inputs=[api_base],
|
| 821 |
outputs=[gateway_status],
|
| 822 |
js=INIT_STREAM_WATCH_JS,
|
| 823 |
)
|
|
|
|
| 1 |
"""
|
| 2 |
+
Gradio demo — HIRO Translation API.
|
| 3 |
|
| 4 |
+
Calls the public connect gateway by default:
|
| 5 |
+
https://connect.zhihuiya.com/hiro_translation/translate
|
| 6 |
+
|
| 7 |
+
Set HIRO_API_KEY or paste a Bearer token in Advanced options.
|
| 8 |
"""
|
| 9 |
|
| 10 |
from __future__ import annotations
|
|
|
|
| 230 |
]
|
| 231 |
|
| 232 |
EXAMPLES = [
|
| 233 |
+
["本发明公开了一种档案管理文件储存用分类标识装置。", "zh", "en"],
|
| 234 |
[
|
| 235 |
+
"本发明公开了一种档案管理文件储存用分类标识装置。\n我是智慧芽的研发",
|
|
|
|
| 236 |
"zh",
|
| 237 |
+
"en",
|
| 238 |
],
|
| 239 |
[
|
| 240 |
+
"The present invention relates to a heat exchanger comprising a plurality of fluid passages.",
|
|
|
|
| 241 |
"en",
|
| 242 |
+
"zh",
|
| 243 |
],
|
| 244 |
[
|
| 245 |
+
"1.一种蓝莓早促快发春梢的绿色生产技术,其特征在于,包括:(1)新主枝的培养、(2)调节树体结构。",
|
| 246 |
"zh",
|
| 247 |
"en",
|
| 248 |
],
|
|
|
|
| 256 |
return (api_base or DEFAULT_BASE).strip().rstrip("/")
|
| 257 |
|
| 258 |
|
| 259 |
+
def _has_health_endpoint(base: str) -> bool:
|
| 260 |
+
"""Only the internal gateway exposes /health; the public connect gateway does not.
|
| 261 |
+
|
| 262 |
+
The public OpenAPI gateway (connect.zhihuiya.com) only routes the purchased
|
| 263 |
+
endpoints (e.g. /translate); probing /health there returns a permission/quota
|
| 264 |
+
error, not a real health signal.
|
| 265 |
+
"""
|
| 266 |
+
return "/compute/hiro_translation_api" in base
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def check_service(api_base: str, api_key: str = "") -> str:
|
| 270 |
base = _resolve_base(api_base)
|
| 271 |
+
|
| 272 |
+
if not _has_health_endpoint(base):
|
| 273 |
+
return (
|
| 274 |
+
f'<span class="status-pill ok">● 公开网关<br>'
|
| 275 |
+
f'<span style="opacity:0.85">调用时以 Bearer 鉴权 · {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">● 在线<br>'
|
|
|
|
| 591 |
"""
|
| 592 |
|
| 593 |
|
| 594 |
+
def run_translation(text: str, lang: str, mode: str, api_base: str, api_key: str = ""):
|
| 595 |
text = (text or "").strip()
|
| 596 |
if not text:
|
| 597 |
yield _empty_outputs()
|
| 598 |
return
|
| 599 |
|
| 600 |
base = _resolve_base(api_base)
|
| 601 |
+
auth = api_key or None
|
| 602 |
pairs: list[tuple[str, str]] = []
|
| 603 |
t0 = time.perf_counter()
|
| 604 |
last_progress = "0/0"
|
|
|
|
| 625 |
|
| 626 |
try:
|
| 627 |
if mode == "fast":
|
| 628 |
+
data = translate_fast(text, lang, base_url=base, api_key=auth)
|
| 629 |
orig = data.get("text_original", text)
|
| 630 |
trans = data.get("text_translated", "")
|
|
|
|
| 631 |
elapsed = time.perf_counter() - t0
|
| 632 |
pairs = [(orig, trans)]
|
| 633 |
status = f"完成 · fast 模式 · {elapsed:.1f}s"
|
| 634 |
+
out_chars = data.get("translated_character_count")
|
| 635 |
+
if out_chars is not None:
|
| 636 |
+
status += f" · {out_chars} chars"
|
| 637 |
yield emit(percent=100, status=status, force=True)
|
| 638 |
return
|
| 639 |
|
| 640 |
+
for chunk in stream_translate(text, lang, base_url=base, api_key=auth):
|
| 641 |
orig = chunk.get("text_original") or ""
|
| 642 |
trans = chunk.get("text_translated") or ""
|
| 643 |
if orig or trans:
|
|
|
|
| 703 |
) as demo:
|
| 704 |
with gr.Sidebar():
|
| 705 |
gr.HTML('<p class="main-title">HIRO Translation</p>')
|
| 706 |
+
gr.Markdown("专利翻译 · `connect.zhihuiya.com` / `hiro-translation-api`")
|
| 707 |
|
| 708 |
gateway_status = gr.HTML(value=check_service(DEFAULT_BASE))
|
| 709 |
refresh_btn = gr.Button("检查网关", size="sm", variant="secondary")
|
|
|
|
| 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-... 或设置环境变量 HIRO_API_KEY",
|
| 741 |
)
|
| 742 |
|
| 743 |
gr.Markdown("**快速示例**")
|
|
|
|
| 781 |
elem_id="hiro-stream-state",
|
| 782 |
)
|
| 783 |
|
| 784 |
+
refresh_btn.click(check_service, inputs=[api_base, api_key], outputs=[gateway_status])
|
| 785 |
+
api_base.change(check_service, inputs=[api_base, api_key], outputs=[gateway_status])
|
| 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 |
|
| 804 |
submit_btn.click(
|
| 805 |
fn=run_translation,
|
| 806 |
+
inputs=[input_text, lang, mode, api_base, api_key],
|
| 807 |
outputs=[stream_state, results_panel, copy_btn],
|
| 808 |
js=RESET_STREAM_JS,
|
| 809 |
)
|
|
|
|
| 845 |
|
| 846 |
demo.load(
|
| 847 |
check_service,
|
| 848 |
+
inputs=[api_base, api_key],
|
| 849 |
outputs=[gateway_status],
|
| 850 |
js=INIT_STREAM_WATCH_JS,
|
| 851 |
)
|