Initial local translation API with persistent projects and batch jobs
This commit is contained in:
commit
13492929c6
21 files changed
+1624
No files matched your search
@@ -0,0 +1,82 @@
|
||||
"""Python standard-library client. No third-party packages required.
|
||||
|
||||
python examples/translate_game.py --input strings.json --output translated.json
|
||||
Rerun with the same output path to resume an existing job.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import time
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
def api(base, path, payload=None, method=None):
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8") if payload is not None else None
|
||||
request = Request(base + path, data=data, headers={"Content-Type": "application/json"}, method=method)
|
||||
try:
|
||||
with urlopen(request, timeout=45) as response:
|
||||
return json.load(response)
|
||||
except HTTPError as exc:
|
||||
raise RuntimeError(f"API HTTP {exc.code}: {exc.read().decode('utf-8', errors='replace')}") from exc
|
||||
|
||||
|
||||
def save(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(path.suffix + ".tmp")
|
||||
temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
temporary.replace(path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--base", default="http://127.0.0.1:8787")
|
||||
parser.add_argument("--input", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--project", help="Existing project ID; otherwise create one")
|
||||
parser.add_argument("--project-file", type=Path, help="Project settings JSON used when creating a project")
|
||||
parser.add_argument("--context", default="Fantasy RPG. Characters speak naturally. Preserve names consistently.")
|
||||
parser.add_argument("--target", default="Russian")
|
||||
parser.add_argument("--retry", action="store_true", help="Retry failed items from the saved job")
|
||||
parser.add_argument("--new-job", action="store_true", help="Ignore saved job and translate using current project settings")
|
||||
args = parser.parse_args()
|
||||
base = args.base.rstrip("/")
|
||||
items = json.loads(args.input.read_text(encoding="utf-8"))
|
||||
if not isinstance(items, list) or not all(isinstance(x, dict) and isinstance(x.get("id"), str) and isinstance(x.get("text"), str) for x in items):
|
||||
parser.error("Input must be an array of {id: string, text: string}")
|
||||
if not 1 <= len(items) <= 500:
|
||||
parser.error("Send 1..500 items per saved job; split larger files")
|
||||
state_path = args.output.with_suffix(args.output.suffix + ".job.json")
|
||||
if state_path.exists() and not args.new_job:
|
||||
state = json.loads(state_path.read_text(encoding="utf-8"))
|
||||
if state["base"] != base or state["items"] != items or (args.project and args.project != state["project_id"]):
|
||||
parser.error("Saved job belongs to a different input, project or server; use --new-job")
|
||||
job = api(base, "/jobs/" + state["job_id"])
|
||||
if args.retry and job["status"] in {"failed", "completed_with_errors"}:
|
||||
job = api(base, "/jobs/" + job["id"] + "/retry", {})
|
||||
else:
|
||||
project_id = args.project
|
||||
if not project_id:
|
||||
project_config = json.loads(args.project_file.read_text(encoding="utf-8")) if args.project_file else {
|
||||
"name": args.input.stem, "target_language": args.target, "context": args.context,
|
||||
"glossary": {"Moonstone": "Лунный камень"}}
|
||||
project = api(base, "/projects", project_config)
|
||||
project_id = project["id"]
|
||||
job = api(base, f"/projects/{project_id}/jobs", {"items": items, "batch_size": 10})
|
||||
save(state_path, {"base": base, "project_id": project_id, "job_id": job["id"], "items": items})
|
||||
print(f"Project: {job['project_id']} Job: {job['id']}")
|
||||
while job["status"] in {"queued", "running"}:
|
||||
print(f"Status: {job['status']} {job['progress']}", flush=True)
|
||||
time.sleep(1)
|
||||
job = api(base, "/jobs/" + job["id"])
|
||||
save(args.output, job)
|
||||
print(f"Saved {args.output}: {job['status']}")
|
||||
if job["status"] != "completed":
|
||||
raise SystemExit("Some items failed. See output; rerun with --retry after fixing the cause.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (RuntimeError, URLError, OSError, ValueError) as exc:
|
||||
raise SystemExit(str(exc)) from exc
|
||||
Reference in new issue
Block a user