Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/release-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ jobs:
ls -la textlingo-desktop/src-tauri/binaries
shell: bash

- name: stage PDF offline assets
run: python script/build_pdf_offline_assets.py
shell: bash

- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ jobs:
ls -la textlingo-desktop/src-tauri/binaries
shell: bash

- name: stage PDF offline assets
run: python script/build_pdf_offline_assets.py
shell: bash

- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
56 changes: 56 additions & 0 deletions script/build_pdf_offline_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Stage a subset of babeldoc offline assets into the Tauri resources dir.

The desktop app ships the DocLayout layout model plus the CJK serif fonts so the
first PDF translation runs without a network download (which is slow/flaky and
made the app look frozen). We deliberately bundle only what the OpenKoto
translation path actually uses — the layout model (always) and one serif font
per CJK target language plus the GoNoto fallback — instead of babeldoc's full
~243 MB font set.

Run this AFTER `pip install -e ./textlingo-desktop/pdf-sidecar` (which installs
the pinned babeldoc), and BEFORE the Tauri bundle step.
"""

import shutil
import sys
from pathlib import Path

from babeldoc.assets.assets import (
get_doclayout_onnx_model_path,
get_font_and_metadata,
)

# Serif fonts matching high_level.download_remote_fonts() for zh-CN / zh-TW /
# ja / ko, plus the GoNoto fallback used for every other language.
FONTS = [
"GoNotoKurrent-Regular.ttf",
"SourceHanSerifCN-Regular.ttf",
"SourceHanSerifTW-Regular.ttf",
"SourceHanSerifJP-Regular.ttf",
"SourceHanSerifKR-Regular.ttf",
]


def main() -> int:
repo_root = Path(__file__).resolve().parent.parent
stage = repo_root / "textlingo-desktop" / "src-tauri" / "resources" / "pdf-assets"
(stage / "models").mkdir(parents=True, exist_ok=True)
(stage / "fonts").mkdir(parents=True, exist_ok=True)

model = Path(get_doclayout_onnx_model_path())
shutil.copy2(model, stage / "models" / model.name)
print(f"staged model: {model.name}")

for font in FONTS:
path, _ = get_font_and_metadata(font)
shutil.copy2(Path(path), stage / "fonts" / font)
print(f"staged font: {font}")

total = sum(p.stat().st_size for p in stage.rglob("*") if p.is_file())
print(f"offline assets staged at {stage} ({total / 1024 / 1024:.1f} MB)")
return 0


if __name__ == "__main__":
sys.exit(main())
2 changes: 1 addition & 1 deletion textlingo-desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "openkoto-desktop",
"private": true,
"version": "0.6.12",
"version": "0.6.13",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
41 changes: 41 additions & 0 deletions textlingo-desktop/pdf-sidecar/openkoto_pdf_translator/pdf2zh.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,43 @@ def _emit_openkoto_progress(progress) -> None:
pass


def _seed_offline_assets() -> None:
"""Copy bundled DocLayout model / fonts into babeldoc's cache so the first
translation does not need to download them.

The desktop app sets OPENKOTO_OFFLINE_ASSETS_DIR to a resource folder laid
out as ``<dir>/models/*.onnx`` and ``<dir>/fonts/*.ttf``. Best-effort: any
failure just falls back to babeldoc's normal on-demand download.
"""
src_dir = os.environ.get("OPENKOTO_OFFLINE_ASSETS_DIR")
if not src_dir or not os.path.isdir(src_dir):
return
try:
import shutil
from babeldoc.const import get_cache_file_path

for sub_folder in ("models", "fonts"):
type_dir = os.path.join(src_dir, sub_folder)
if not os.path.isdir(type_dir):
continue
for name in os.listdir(type_dir):
src = os.path.join(type_dir, name)
if not os.path.isfile(src):
continue
# get_cache_file_path creates the destination sub-folder for us.
dst = str(get_cache_file_path(name, sub_folder))
if os.path.exists(dst):
continue
shutil.copy2(src, dst)
print(
f"[PDF] seeded offline asset: {sub_folder}/{name}",
file=sys.stderr,
flush=True,
)
except Exception as e:
print(f"[PDF] offline asset seeding skipped: {e}", file=sys.stderr, flush=True)


def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__, add_help=True)
parser.add_argument(
Expand Down Expand Up @@ -299,6 +336,10 @@ def main(args: Optional[List[str]] = None) -> int:
if parsed_args.debug:
log.setLevel(logging.DEBUG)

# Restore bundled offline assets (if shipped) before any model/font load so
# the first translation does not block on a network download.
_seed_offline_assets()

if parsed_args.onnx:
ModelInstance.value = OnnxModel(parsed_args.onnx)
else:
Expand Down
5 changes: 5 additions & 0 deletions textlingo-desktop/src-tauri/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

# PDF offline assets are generated in CI (script/build_pdf_offline_assets.py),
# not committed (~95 MB). Keep the folder + README, ignore the binaries.
/resources/pdf-assets/models/
/resources/pdf-assets/fonts/
2 changes: 1 addition & 1 deletion textlingo-desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion textlingo-desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "openkoto-desktop"
version = "0.6.12"
version = "0.6.13"
description = "OpenKoto Desktop - AI-powered Article Reader"
authors = ["OpenKoto Team"]
edition = "2021"
Expand Down
20 changes: 20 additions & 0 deletions textlingo-desktop/src-tauri/resources/pdf-assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# PDF offline assets

This folder ships the babeldoc assets the PDF translator needs so the **first**
translation does not have to download them (DocLayout layout model + CJK serif
fonts). It exists so packaged builds can run PDF translation offline.

The actual `models/*.onnx` and `fonts/*.ttf` files are **generated in CI**, not
committed (they are ~95 MB). They are produced by:

```
python script/build_pdf_offline_assets.py
```

which must run after `pip install -e ./textlingo-desktop/pdf-sidecar` and before
the Tauri bundle step. The Rust side points the sidecar at this folder via the
`OPENKOTO_OFFLINE_ASSETS_DIR` env var; the sidecar copies the files into
babeldoc's cache on first run (see `_seed_offline_assets` in `pdf2zh.py`).

In local dev builds where these files are absent, the sidecar simply falls back
to babeldoc's normal on-demand download.
16 changes: 16 additions & 0 deletions textlingo-desktop/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3023,6 +3023,22 @@ pub async fn translate_pdf_document(
envs.push(("OPENKOTO_BASE_URL", url));
}

// Point the sidecar at bundled offline assets (DocLayout model + CJK fonts)
// so the first translation doesn't block on a network download. Absent in
// dev builds, in which case the sidecar falls back to on-demand download.
if let Ok(resource_dir) = app_handle.path().resource_dir() {
for candidate in ["resources/pdf-assets", "pdf-assets"] {
let dir = resource_dir.join(candidate);
if dir.join("models").is_dir() || dir.join("fonts").is_dir() {
envs.push((
"OPENKOTO_OFFLINE_ASSETS_DIR",
dir.to_string_lossy().to_string(),
));
break;
}
}
}

let sidecar = pdf_sidecar::resolve_pdf_sidecar(&app_handle)
.map_err(|e| format!("PDF sidecar error: {e}"))?;
let cmd = sidecar.program;
Expand Down
5 changes: 4 additions & 1 deletion textlingo-desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "OpenKoto Desktop",
"version": "0.6.12",
"version": "0.6.13",
"identifier": "com.openkoto.desktop",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down Expand Up @@ -51,6 +51,9 @@
"binaries/ffmpeg",
"binaries/openkoto-pdf-translator"
],
"resources": [
"resources/pdf-assets"
],
"macOS": {
"entitlements": "entitlements.plist"
}
Expand Down