Spaces:
Sleeping
Sleeping
File size: 5,061 Bytes
067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 9a40f1b 4a9a269 9a40f1b 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 067d7b2 4a9a269 | 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 | """HTTP client for HIRO Translation API (public connect gateway or internal stage)."""
from __future__ import annotations
import json
import os
from collections.abc import Iterator
from typing import Any
import requests
# Public gateway (matches curl examples against connect.zhihuiya.com).
DEFAULT_BASE = "https://connect.zhihuiya.com/hiro_translation"
API_KEY_ENV = "HIRO_API_KEY"
def lang_to_codes(lang: str) -> tuple[str, str]:
src, _, tgt = lang.partition("2")
if not src or not tgt:
raise ValueError(f"invalid lang {lang!r}; expected format like zh2en")
return src, tgt
def translate_payload(
content: str,
lang: str,
*,
mode: str,
) -> dict[str, Any]:
source, target = lang_to_codes(lang)
return {
"content": content,
"sourceLanguageCode": source,
"targetLanguageCode": target,
"mode": mode,
}
def _api_url(base_url: str, path: str) -> str:
return f"{base_url.rstrip('/')}{path}"
def _request_headers(api_key: str | None = None) -> dict[str, str]:
headers = {"Content-Type": "application/json"}
key = (api_key if api_key is not None else os.environ.get(API_KEY_ENV, "")).strip()
if key:
headers["Authorization"] = f"Bearer {key}"
return headers
def _translated_text(data: dict[str, Any]) -> str:
value = data.get("textTranslated", data.get("text_translated", ""))
return value if isinstance(value, str) else ""
def _original_text(data: dict[str, Any], fallback: str = "") -> str:
value = data.get("textOriginal", data.get("text_original", fallback))
return value if isinstance(value, str) else fallback
def health_ok(
base_url: str = DEFAULT_BASE,
*,
api_key: str | None = None,
) -> tuple[bool, str]:
try:
r = requests.get(
_api_url(base_url, "/health"),
headers=_request_headers(api_key),
timeout=12,
)
r.raise_for_status()
data = r.json()
if data.get("status") != "OK":
return False, f"Unexpected response: {r.text[:200]}"
upstream = data.get("upstream", "UNKNOWN")
if upstream == "OK":
return True, "Healthy · upstream OK"
return False, f"Gateway up, upstream unavailable ({upstream})"
except requests.RequestException as exc:
return False, str(exc)
def translate_fast(
text: str,
lang: str,
*,
base_url: str = DEFAULT_BASE,
api_key: str | None = None,
timeout: int = 1800,
) -> dict[str, Any]:
r = requests.post(
_api_url(base_url, "/translate"),
json=translate_payload(text, lang, mode="fast"),
headers=_request_headers(api_key),
timeout=timeout,
)
if r.status_code >= 400:
try:
err = r.json().get("error", r.text[:500])
except json.JSONDecodeError:
err = r.text[:500]
raise RuntimeError(err)
data = r.json()
if data.get("state") not in (None, "success") and "error" in data:
raise RuntimeError(str(data.get("error", data)))
# Normalize to keys the UI already understands (snake_case aliases).
return {
"state": data.get("state", "success"),
"text_original": _original_text(data, text),
"text_translated": _translated_text(data),
"translated_character_count": data.get("translatedCharacterCount"),
"billing_amount": r.headers.get("X-Openapi-Amount"),
"raw": data,
}
def _iter_sse_json(resp: requests.Response) -> Iterator[dict[str, Any]]:
for raw in resp.iter_lines(decode_unicode=True):
if not raw:
continue
line = raw.strip()
if not line.startswith("data:"):
continue
payload = line[5:].lstrip()
if not payload:
continue
chunk = json.loads(payload)
if isinstance(chunk, dict) and chunk.get("error"):
raise RuntimeError(str(chunk["error"]))
if isinstance(chunk, dict):
yield {
"state": chunk.get("state", "success"),
"text_original": _original_text(chunk),
"text_translated": _translated_text(chunk),
"translated_character_count": chunk.get("translatedCharacterCount"),
"progress": chunk.get("progress"),
"raw": chunk,
}
def stream_translate(
text: str,
lang: str,
*,
base_url: str = DEFAULT_BASE,
api_key: str | None = None,
timeout: int = 1800,
) -> Iterator[dict[str, Any]]:
with requests.post(
_api_url(base_url, "/translate"),
json=translate_payload(text, lang, mode="stream"),
headers=_request_headers(api_key),
stream=True,
timeout=timeout,
) as resp:
if resp.status_code >= 400:
try:
err = resp.json().get("error", resp.text[:500])
except (json.JSONDecodeError, ValueError):
err = resp.text[:500]
raise RuntimeError(err)
yield from _iter_sse_json(resp)
|