Jiawei Dong commited on
Commit
b1b2cec
·
1 Parent(s): bbc4064
.gitignore CHANGED
@@ -5,3 +5,4 @@ __pycache__/
5
  venv/
6
  .env
7
  .DS_Store
 
 
5
  venv/
6
  .env
7
  .DS_Store
8
+ .gradio_tmp/
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: HIRO Translation
3
  emoji: 🦀
4
  colorFrom: red
5
  colorTo: indigo
@@ -9,33 +9,43 @@ python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
- short_description: Patent translation demo via hiro-translation-api
13
  ---
14
 
15
- # HIRO Translation
16
 
17
- Patent translation demo for the HIRO Translation API.
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 |
27
  |------|---------|
28
- | `app.py` | Gradio UI |
29
- | `api_client.py` | HTTP client for `/translate` (camelCase request/response) |
30
  | `requirements.txt` | Python dependencies (`requests`; Gradio is pre-installed on Spaces) |
31
 
32
  ## Local run
33
 
34
  ```bash
35
  pip install -r requirements.txt
36
- export HIRO_API_KEY="sk-..." # optional if set in UI
37
  python app.py
38
  ```
39
 
40
  Check out the [Spaces config reference](https://huggingface.co/docs/hub/spaces-config-reference).
41
-
 
1
  ---
2
+ title: Document Processing
3
  emoji: 🦀
4
  colorFrom: red
5
  colorTo: indigo
 
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
+ short_description: Smart Doc + patent translation via connect gateway
13
  ---
14
 
15
+ # Document Processing
16
 
17
+ Gradio demo with two tabs sharing one Bearer API key from the environment.
18
 
19
+ ## Tabs
20
+
21
+ | Tab | Gateway | Notes |
22
+ |-----|---------|--------|
23
+ | **Smart Doc** | `https://connect.zhihuiya.com/rd-llm/v1/documents/doc_parsing` | Multipart upload · `output_format=both` · **10 MB** max |
24
+ | **Translation** | `https://connect.zhihuiya.com/hiro_translation` | `stream` (SSE segments) or `fast` (single JSON) |
25
+
26
+ ## Auth
27
+
28
+ Set under Space **Settings → Variables / Secrets** (or local env):
29
+
30
+ 1. `HIRO_API_KEY` (preferred)
31
+ 2. else `RD_LLM_API_KEY`
32
+
33
+ No API key field in the UI.
34
 
35
  ## Files
36
 
37
  | File | Purpose |
38
  |------|---------|
39
+ | `app.py` | Gradio UI (tabs + streaming translation + document parse) |
40
+ | `api_client.py` | HTTP clients for translation + Smart Doc |
41
  | `requirements.txt` | Python dependencies (`requests`; Gradio is pre-installed on Spaces) |
42
 
43
  ## Local run
44
 
45
  ```bash
46
  pip install -r requirements.txt
47
+ export HIRO_API_KEY="sk-..." # or RD_LLM_API_KEY
48
  python app.py
49
  ```
50
 
51
  Check out the [Spaces config reference](https://huggingface.co/docs/hub/spaces-config-reference).
 
__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
 
__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,18 +1,34 @@
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]:
@@ -41,9 +57,15 @@ 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
@@ -105,7 +127,6 @@ def translate_fast(
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),
@@ -162,3 +183,78 @@ def stream_translate(
162
  err = resp.text[:500]
163
  raise RuntimeError(err)
164
  yield from _iter_sse_json(resp)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HTTP clients for HIRO Translation and Smart Doc (public connect gateway)."""
2
 
3
  from __future__ import annotations
4
 
5
  import json
6
  import os
7
+ import re
8
  from collections.abc import Iterator
9
+ from pathlib import Path
10
  from typing import Any
11
 
12
  import requests
13
 
14
+ # Public gateways (matches connect.zhihuiya.com curl examples).
15
  DEFAULT_BASE = "https://connect.zhihuiya.com/hiro_translation"
16
+ SMARTDOC_URL = "https://connect.zhihuiya.com/rd-llm/v1/documents/doc_parsing"
17
 
18
  API_KEY_ENV = "HIRO_API_KEY"
19
+ API_KEY_ENV_FALLBACK = "RD_LLM_API_KEY"
20
+
21
+ MAX_SMARTDOC_BYTES = 10 * 1024 * 1024
22
+
23
+
24
+ def resolve_api_key(api_key: str | None = None) -> str:
25
+ """Prefer explicit key, then HIRO_API_KEY, then RD_LLM_API_KEY."""
26
+ if api_key is not None and str(api_key).strip():
27
+ return str(api_key).strip()
28
+ return (
29
+ os.environ.get(API_KEY_ENV, "").strip()
30
+ or os.environ.get(API_KEY_ENV_FALLBACK, "").strip()
31
+ )
32
 
33
 
34
  def lang_to_codes(lang: str) -> tuple[str, str]:
 
57
  return f"{base_url.rstrip('/')}{path}"
58
 
59
 
60
+ def _request_headers(
61
+ api_key: str | None = None,
62
+ *,
63
+ json_body: bool = True,
64
+ ) -> dict[str, str]:
65
+ headers: dict[str, str] = {}
66
+ if json_body:
67
+ headers["Content-Type"] = "application/json"
68
+ key = resolve_api_key(api_key)
69
  if key:
70
  headers["Authorization"] = f"Bearer {key}"
71
  return headers
 
127
  data = r.json()
128
  if data.get("state") not in (None, "success") and "error" in data:
129
  raise RuntimeError(str(data.get("error", data)))
 
130
  return {
131
  "state": data.get("state", "success"),
132
  "text_original": _original_text(data, text),
 
183
  err = resp.text[:500]
184
  raise RuntimeError(err)
185
  yield from _iter_sse_json(resp)
186
+
187
+
188
+ def normalize_smartdoc_markdown(markdown: str) -> str:
189
+ """Fix gateway-escaped newlines without breaking LaTeX commands like \\neq."""
190
+ text = markdown
191
+ text = re.sub(r"\\r\\n(?![A-Za-z])", "\n", text)
192
+ text = re.sub(r"\\n(?![A-Za-z])", "\n", text)
193
+ text = re.sub(r"\\r(?![A-Za-z])", "\n", text)
194
+ return text
195
+
196
+
197
+ def parse_document(
198
+ file_path: str,
199
+ *,
200
+ api_key: str | None = None,
201
+ output_format: str = "both",
202
+ timeout: int = 300,
203
+ max_bytes: int = MAX_SMARTDOC_BYTES,
204
+ ) -> dict[str, Any]:
205
+ """POST multipart to Smart Doc doc_parsing endpoint."""
206
+ path = Path(file_path)
207
+ if not path.is_file():
208
+ raise RuntimeError(f"file not found: {file_path}")
209
+ size = path.stat().st_size
210
+ if size > max_bytes:
211
+ raise RuntimeError(
212
+ f"The file must not exceed {max_bytes // (1024 * 1024)} MB"
213
+ )
214
+
215
+ key = resolve_api_key(api_key)
216
+ if not key:
217
+ raise RuntimeError(
218
+ f"API key missing. Set {API_KEY_ENV} (or {API_KEY_ENV_FALLBACK}) "
219
+ "under Space Settings → Variables / Secrets"
220
+ )
221
+
222
+ with path.open("rb") as fh:
223
+ files = {"file": (path.name, fh)}
224
+ data = {"output_format": output_format}
225
+ r = requests.post(
226
+ SMARTDOC_URL,
227
+ headers=_request_headers(key, json_body=False),
228
+ files=files,
229
+ data=data,
230
+ timeout=timeout,
231
+ )
232
+
233
+ try:
234
+ body = r.json()
235
+ except json.JSONDecodeError as exc:
236
+ raise RuntimeError(f"HTTP {r.status_code}: {r.text[:500]}") from exc
237
+
238
+ if r.status_code >= 400 or body.get("status") != "success":
239
+ raise RuntimeError(
240
+ body.get("error_msg")
241
+ or body.get("message")
242
+ or body.get("error")
243
+ or f"HTTP {r.status_code}"
244
+ )
245
+
246
+ payload = body.get("data") if isinstance(body.get("data"), dict) else body
247
+ markdown = payload.get("markdown") if isinstance(payload, dict) else ""
248
+ if not isinstance(markdown, str):
249
+ markdown = ""
250
+ results = payload.get("results") if isinstance(payload, dict) else []
251
+ if not isinstance(results, list):
252
+ results = []
253
+ total_pages = payload.get("total_pages") if isinstance(payload, dict) else 0
254
+
255
+ return {
256
+ "markdown": normalize_smartdoc_markdown(markdown),
257
+ "results": results,
258
+ "total_pages": total_pages or 0,
259
+ "raw": body,
260
+ }
app.py CHANGED
@@ -1,21 +1,37 @@
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
11
 
12
  import html
13
  import json
 
14
  import time
 
 
 
 
 
 
15
 
16
  import gradio as gr
17
 
18
- from api_client import DEFAULT_BASE, health_ok, stream_translate, translate_fast
 
 
 
 
 
 
 
 
19
 
20
  CUSTOM_CSS = """
21
  .gradio-container {
@@ -187,6 +203,14 @@ CUSTOM_CSS = """
187
  }
188
 
189
  footer { display: none !important; }
 
 
 
 
 
 
 
 
190
  """
191
 
192
  RESULTS_SHELL_HTML = """
@@ -266,21 +290,23 @@ def _has_health_endpoint(base: str) -> bool:
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>'
@@ -575,14 +601,14 @@ INIT_STREAM_WATCH_JS = """
575
  """
576
 
577
 
578
- def run_translation(text: str, lang: str, mode: str, api_key: str = ""):
579
  text = (text or "").strip()
580
  if not text:
581
  yield _empty_outputs()
582
  return
583
 
584
  base = _resolve_base(DEFAULT_BASE)
585
- auth = api_key or None
586
  pairs: list[tuple[str, str]] = []
587
  t0 = time.perf_counter()
588
  last_progress = "0/0"
@@ -674,6 +700,44 @@ def run_translation(text: str, lang: str, mode: str, api_key: str = ""):
674
  yield _pack_outputs(stream_payload=payload, panel_visible=True, copy_enabled=False)
675
 
676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  def build_theme() -> gr.Theme:
678
  return gr.themes.Soft(
679
  primary_hue="blue",
@@ -687,91 +751,140 @@ def build_theme() -> gr.Theme:
687
 
688
  def build_ui() -> gr.Blocks:
689
  with gr.Blocks(
690
- title="HIRO Translation",
691
  css=CUSTOM_CSS,
692
  theme=build_theme(),
693
  ) as demo:
694
  with gr.Sidebar():
695
- gr.HTML('<p class="main-title">HIRO Translation</p>')
696
  gr.Markdown(
697
- "Patent translation · `connect.zhihuiya.com` / `hiro-translation-api`"
 
698
  )
699
-
700
  gateway_status = gr.HTML(value=check_service())
701
- refresh_btn = gr.Button("Check gateway", size="sm", variant="secondary")
702
 
703
- with gr.Row():
704
- source_lang = gr.Dropdown(
705
- choices=LANG_OPTIONS,
706
- value="zh",
707
- label="Source language",
708
  )
709
- target_lang = gr.Dropdown(
710
- choices=LANG_OPTIONS,
711
- value="en",
712
- label="Target language",
713
- )
714
- lang = gr.Textbox(value="zh2en", visible=False)
715
- mode = gr.Radio(
716
- choices=["stream", "fast"],
717
- value="stream",
718
- label="Mode",
719
- info="stream: segment streaming; fast: single full response",
720
- )
721
-
722
- with gr.Accordion("Advanced", open=False):
723
- api_key = gr.Textbox(
724
- label="API Key (Bearer)",
725
- type="password",
726
- placeholder="sk-... or set HIRO_API_KEY",
 
727
  )
728
-
729
- gr.Markdown("**Examples**")
730
- example_btns: list[tuple[gr.Button, str, str, str]] = []
731
- for i, (sample, src, tgt) in enumerate(EXAMPLES, start=1):
732
- preview = sample.replace("\n", " ")[:36]
733
- if len(sample) > 36:
734
- preview += "…"
735
- btn = gr.Button(f"{i}. {preview}", size="sm", variant="secondary")
736
- example_btns.append((btn, sample, src, tgt))
737
-
738
- with gr.Column():
739
- input_text = gr.Textbox(
740
- label="Source text",
741
- placeholder=(
742
- "Enter patent-related text. Supports Chinese, English, "
743
- "Japanese, Korean, and more."
744
- ),
745
- lines=8,
746
- elem_id="input-box",
747
- )
748
- with gr.Row(elem_classes=["toolbar-row"]):
749
- submit_btn = gr.Button("Translate", variant="primary", scale=0)
750
- clear_btn = gr.Button("Clear", scale=0)
751
- copy_btn = gr.Button(
752
- "Copy full translation",
753
- variant="secondary",
754
- interactive=False,
755
- scale=0,
756
- elem_id="copy-translation-btn",
757
  )
758
-
759
- with gr.Column(visible=False, elem_classes=["results-panel-col"]) as results_panel:
760
- gr.HTML(RESULTS_SHELL_HTML, elem_id="translation-results")
761
- stream_state = gr.Textbox(
762
- value=_stream_state_payload(
763
- visible_pairs=[],
764
- full="",
765
- lang="zh2en",
766
- percent=0,
767
- status="Ready",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
768
  ),
769
- visible=False,
770
- elem_id="hiro-stream-state",
771
  )
772
-
773
- refresh_btn.click(check_service, inputs=[api_key], outputs=[gateway_status])
774
- api_key.change(check_service, inputs=[api_key], outputs=[gateway_status])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
 
776
  def _combine_lang(src: str, tgt: str) -> str:
777
  return f"{src}2{tgt}"
@@ -791,7 +904,7 @@ def build_ui() -> gr.Blocks:
791
 
792
  submit_btn.click(
793
  fn=run_translation,
794
- inputs=[input_text, lang, mode, api_key],
795
  outputs=[stream_state, results_panel, copy_btn],
796
  )
797
 
@@ -829,9 +942,27 @@ def build_ui() -> gr.Blocks:
829
  outputs=[input_text, stream_state, results_panel, copy_btn],
830
  )
831
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  demo.load(
833
  check_service,
834
- inputs=[api_key],
835
  outputs=[gateway_status],
836
  js=INIT_STREAM_WATCH_JS,
837
  )
 
1
  """
2
+ Gradio demo — Document Processing (Smart Doc + Translation).
3
 
4
+ Tabs:
5
+ - Smart Doc → https://connect.zhihuiya.com/rd-llm/v1/documents/doc_parsing
6
+ - Translation → https://connect.zhihuiya.com/hiro_translation
7
 
8
+ Auth: env HIRO_API_KEY, fallback RD_LLM_API_KEY (shared for both tabs).
9
  """
10
 
11
  from __future__ import annotations
12
 
13
  import html
14
  import json
15
+ import os
16
  import time
17
+ from pathlib import Path
18
+
19
+ # Shared hosts often leave /tmp/gradio owned by another user → PermissionError on upload.
20
+ _GRADIO_TMP = Path(__file__).resolve().parent / ".gradio_tmp"
21
+ _GRADIO_TMP.mkdir(parents=True, exist_ok=True)
22
+ os.environ.setdefault("GRADIO_TEMP_DIR", str(_GRADIO_TMP))
23
 
24
  import gradio as gr
25
 
26
+ from api_client import (
27
+ DEFAULT_BASE,
28
+ SMARTDOC_URL,
29
+ health_ok,
30
+ parse_document,
31
+ resolve_api_key,
32
+ stream_translate,
33
+ translate_fast,
34
+ )
35
 
36
  CUSTOM_CSS = """
37
  .gradio-container {
 
203
  }
204
 
205
  footer { display: none !important; }
206
+
207
+ .smartdoc-status {
208
+ font-size: 0.9rem;
209
+ color: var(--body-text-color-subdued);
210
+ margin: 0.25rem 0 0.75rem 0;
211
+ }
212
+ .smartdoc-status.ok { color: var(--color-accent, #047857); }
213
+ .smartdoc-status.err { color: var(--error-text-color, #b42318); }
214
  """
215
 
216
  RESULTS_SHELL_HTML = """
 
290
  return "/compute/hiro_translation_api" in base
291
 
292
 
293
+ def check_service() -> str:
294
  base = _resolve_base(DEFAULT_BASE)
295
+ key = resolve_api_key()
296
+ key_hint = "HIRO_API_KEY / RD_LLM_API_KEY" if not key else "API key loaded from env"
297
 
298
  if not _has_health_endpoint(base):
299
  return (
300
  f'<span class="status-pill ok">● Public gateway<br>'
301
+ f'<span style="opacity:0.85">{html.escape(base)} · {html.escape(key_hint)}</span>'
302
  f"</span>"
303
  )
304
 
305
+ ok, msg = health_ok(base, api_key=key or None)
306
  if ok:
307
  return (
308
  f'<span class="status-pill ok">● Online<br>'
309
+ f'<span style="opacity:0.85">{html.escape(base)} · {html.escape(key_hint)}</span></span>'
310
  )
311
  return (
312
  f'<span class="status-pill err">● Unavailable<br>'
 
601
  """
602
 
603
 
604
+ def run_translation(text: str, lang: str, mode: str):
605
  text = (text or "").strip()
606
  if not text:
607
  yield _empty_outputs()
608
  return
609
 
610
  base = _resolve_base(DEFAULT_BASE)
611
+ auth = resolve_api_key() or None
612
  pairs: list[tuple[str, str]] = []
613
  t0 = time.perf_counter()
614
  last_progress = "0/0"
 
700
  yield _pack_outputs(stream_payload=payload, panel_visible=True, copy_enabled=False)
701
 
702
 
703
+ def _file_path(file_obj) -> str | None:
704
+ if file_obj is None:
705
+ return None
706
+ if isinstance(file_obj, (list, tuple)):
707
+ return _file_path(file_obj[0]) if file_obj else None
708
+ if isinstance(file_obj, str):
709
+ return file_obj
710
+ return getattr(file_obj, "name", None) or getattr(file_obj, "path", None)
711
+
712
+
713
+ def run_smartdoc(file_obj):
714
+ path = _file_path(file_obj)
715
+ if not path:
716
+ return (
717
+ '<p class="smartdoc-status err">Please select a file first</p>',
718
+ "*Rendered Markdown will appear here after parsing.*",
719
+ "[]",
720
+ )
721
+ try:
722
+ data = parse_document(path)
723
+ pages = data.get("total_pages") or 0
724
+ results = data.get("results") or []
725
+ markdown = data.get("markdown") or ""
726
+ status = (
727
+ f'<p class="smartdoc-status ok">Complete · {pages} pages · '
728
+ f"{len(results)} results</p>"
729
+ )
730
+ if not markdown.strip():
731
+ markdown = "*No Markdown content was returned.*"
732
+ return status, markdown, json.dumps(results, ensure_ascii=False, indent=2)
733
+ except Exception as exc:
734
+ return (
735
+ f'<p class="smartdoc-status err">Parsing failed: {html.escape(str(exc))}</p>',
736
+ "*Parsing failed. Check the status message.*",
737
+ "[]",
738
+ )
739
+
740
+
741
  def build_theme() -> gr.Theme:
742
  return gr.themes.Soft(
743
  primary_hue="blue",
 
751
 
752
  def build_ui() -> gr.Blocks:
753
  with gr.Blocks(
754
+ title="Document Processing",
755
  css=CUSTOM_CSS,
756
  theme=build_theme(),
757
  ) as demo:
758
  with gr.Sidebar():
759
+ gr.HTML('<p class="main-title">Document Processing</p>')
760
  gr.Markdown(
761
+ "Smart Doc + Translation · shared Bearer key from "
762
+ "`HIRO_API_KEY` (fallback `RD_LLM_API_KEY`)"
763
  )
 
764
  gateway_status = gr.HTML(value=check_service())
765
+ refresh_btn = gr.Button("Refresh status", size="sm", variant="secondary")
766
 
767
+ with gr.Tabs():
768
+ with gr.Tab("Smart Doc"):
769
+ gr.Markdown(
770
+ f"Upload a document for layout + OCR parsing via "
771
+ f"`{SMARTDOC_URL}` · PDF / image / Office · **10 MB** max"
772
  )
773
+ doc_file = gr.File(
774
+ label="Document",
775
+ file_count="single",
776
+ file_types=[
777
+ ".pdf",
778
+ ".png",
779
+ ".jpg",
780
+ ".jpeg",
781
+ ".doc",
782
+ ".docx",
783
+ ".ppt",
784
+ ".pptx",
785
+ ".xls",
786
+ ".xlsx",
787
+ ".odt",
788
+ ".odp",
789
+ ".ods",
790
+ ".rtf",
791
+ ],
792
  )
793
+ with gr.Row(elem_classes=["toolbar-row"]):
794
+ parse_btn = gr.Button("Parse Document", variant="primary", scale=0)
795
+ clear_doc_btn = gr.Button("Clear", scale=0)
796
+ smartdoc_status = gr.HTML(
797
+ value='<p class="smartdoc-status">Select a file to begin</p>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  )
799
+ with gr.Tabs():
800
+ with gr.Tab("Markdown"):
801
+ smartdoc_md = gr.Markdown(
802
+ value="*Rendered Markdown will appear here after parsing.*",
803
+ latex_delimiters=[
804
+ {"left": "$$", "right": "$$", "display": True},
805
+ {"left": "$", "right": "$", "display": False},
806
+ {"left": "\\(", "right": "\\)", "display": False},
807
+ {"left": "\\[", "right": "\\]", "display": True},
808
+ ],
809
+ )
810
+ with gr.Tab("Results"):
811
+ smartdoc_json = gr.Code(
812
+ value="[]",
813
+ language="json",
814
+ label="Structured results",
815
+ lines=22,
816
+ )
817
+
818
+ with gr.Tab("Translation"):
819
+ with gr.Row():
820
+ source_lang = gr.Dropdown(
821
+ choices=LANG_OPTIONS,
822
+ value="zh",
823
+ label="Source language",
824
+ scale=1,
825
+ )
826
+ target_lang = gr.Dropdown(
827
+ choices=LANG_OPTIONS,
828
+ value="en",
829
+ label="Target language",
830
+ scale=1,
831
+ )
832
+ mode = gr.Radio(
833
+ choices=["stream", "fast"],
834
+ value="stream",
835
+ label="Mode",
836
+ info="stream: segment streaming; fast: single full response",
837
+ scale=1,
838
+ )
839
+ lang = gr.Textbox(value="zh2en", visible=False)
840
+
841
+ gr.Markdown("**Examples**")
842
+ example_btns: list[tuple[gr.Button, str, str, str]] = []
843
+ with gr.Row():
844
+ for i, (sample, src, tgt) in enumerate(EXAMPLES, start=1):
845
+ preview = sample.replace("\n", " ")[:28]
846
+ if len(sample) > 28:
847
+ preview += "…"
848
+ btn = gr.Button(f"{i}. {preview}", size="sm", variant="secondary")
849
+ example_btns.append((btn, sample, src, tgt))
850
+
851
+ input_text = gr.Textbox(
852
+ label="Source text",
853
+ placeholder=(
854
+ "Enter patent-related text. Supports Chinese, English, "
855
+ "Japanese, Korean, and more."
856
  ),
857
+ lines=8,
858
+ elem_id="input-box",
859
  )
860
+ with gr.Row(elem_classes=["toolbar-row"]):
861
+ submit_btn = gr.Button("Translate", variant="primary", scale=0)
862
+ clear_btn = gr.Button("Clear", scale=0)
863
+ copy_btn = gr.Button(
864
+ "Copy full translation",
865
+ variant="secondary",
866
+ interactive=False,
867
+ scale=0,
868
+ elem_id="copy-translation-btn",
869
+ )
870
+
871
+ with gr.Column(
872
+ visible=False, elem_classes=["results-panel-col"]
873
+ ) as results_panel:
874
+ gr.HTML(RESULTS_SHELL_HTML, elem_id="translation-results")
875
+ stream_state = gr.Textbox(
876
+ value=_stream_state_payload(
877
+ visible_pairs=[],
878
+ full="",
879
+ lang="zh2en",
880
+ percent=0,
881
+ status="Ready",
882
+ ),
883
+ visible=False,
884
+ elem_id="hiro-stream-state",
885
+ )
886
+
887
+ refresh_btn.click(check_service, outputs=[gateway_status])
888
 
889
  def _combine_lang(src: str, tgt: str) -> str:
890
  return f"{src}2{tgt}"
 
904
 
905
  submit_btn.click(
906
  fn=run_translation,
907
+ inputs=[input_text, lang, mode],
908
  outputs=[stream_state, results_panel, copy_btn],
909
  )
910
 
 
942
  outputs=[input_text, stream_state, results_panel, copy_btn],
943
  )
944
 
945
+ parse_btn.click(
946
+ fn=run_smartdoc,
947
+ inputs=[doc_file],
948
+ outputs=[smartdoc_status, smartdoc_md, smartdoc_json],
949
+ )
950
+
951
+ def on_clear_doc():
952
+ return (
953
+ None,
954
+ '<p class="smartdoc-status">Select a file to begin</p>',
955
+ "*Rendered Markdown will appear here after parsing.*",
956
+ "[]",
957
+ )
958
+
959
+ clear_doc_btn.click(
960
+ on_clear_doc,
961
+ outputs=[doc_file, smartdoc_status, smartdoc_md, smartdoc_json],
962
+ )
963
+
964
  demo.load(
965
  check_service,
 
966
  outputs=[gateway_status],
967
  js=INIT_STREAM_WATCH_JS,
968
  )