How to build and run PDFusion from source. If you just want to use the app, the README has the installers.
PDFusion is a Tauri 2 (Rust shell) + React + TypeScript + Tailwind + shadcn/ui desktop app. The Python translation/RAG backend runs as a FastAPI sidecar that the Tauri shell spawns on app start. For why the pieces fit together the way they do, see the architecture notes.
Windows and Linux are supported and built by CI; macOS builds from source but is not packaged yet (see Platform support).
| Windows | Linux | macOS | |
|---|---|---|---|
| Runs from source | yes | yes | yes, untested |
| Packaged by CI | .exe (NSIS) |
.deb |
not yet |
| API keys stored in | DPAPI | Secret Service (gnome-keyring, KWallet) | Keychain |
| Data root | %LOCALAPPDATA%\PDFusion |
$XDG_DATA_HOME/PDFusion, else ~/.local/share/PDFusion |
~/Library/Application Support/PDFusion |
macOS is wired up — a dmg target, the Keychain and the data root are all in
place — but nobody has run it on a Mac, and an unsigned, un-notarized .dmg is
refused by Gatekeeper, so no macOS artifact is published. See issue #69.
On Linux with no keyring running (a bare tiling-WM setup, a container), keys
fall back to an obfuscated value in config.toml that anyone who can read the
file can recover. The app says so in app.log when it happens. Install and
unlock gnome-keyring or kwalletmanager if that matters to you.
- Python 3.11 (via Anaconda/Miniforge) — for the FastAPI sidecar
- Node.js ≥ 18 + pnpm — for the React frontend
- Rust (rustup, cargo) — for the Tauri shell
Per platform:
-
Windows — Microsoft Visual C++ Build Tools 2022/2026 (the Rust MSVC linker), and the WebView2 Runtime (ships with Windows 11; install separately on Windows 10).
-
Linux — webkit2gtk 4.1 (not 4.0, which is Tauri 1) and its build headers. On Debian/Ubuntu:
sudo apt install libwebkit2gtk-4.1-dev libgtk-3-dev \ libayatana-appindicator3-dev librsvg2-dev patchelf \ build-essential curl wget file libssl-dev libxdo-devFedora:
webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel librsvg2-devel patchelf. Arch:webkit2gtk-4.1 gtk3 libappindicator-gtk3 librsvg patchelf. -
macOS — Xcode command line tools (
xcode-select --install). WKWebView is part of the OS.
conda create -n pdfusion python=3.11.14
conda activate pdfusion
pip install -r requirements.txt
# Optional extras (editable install — the package isn't published to PyPI):
pip install -e ".[rag]" # RAG chat (ChromaDB, ONNX embeddings, RapidOCR)These examples name the env
pdfusion;pdfusion-envalso works out of the box. The Tauri shell auto-detects either name underanaconda3,miniconda3orminiforge3in your home directory, on every platform. If you use something else, setPDFUSION_PYTHONto that env's interpreter (python.exeon Windows,bin/pythonelsewhere) before launching the desktop app.
cd desktop
pnpm installIf bare
pnpmisn't resolvable even aftercorepack enable(it can fail withEPERMwriting shims intoProgram Files\nodejswithout admin rights), install it globally instead:npm install -g pnpm.
Create a .env file in the project root:
OPENAI_API_KEY=...
GEMINI_API_KEY=...
ANTHROPIC_API_KEY=... # optionalYou can also enter and validate keys later from the in-app Settings sheet (they're encrypted before being written to disk).
Pointing PDFusion at a proxy or another service compatible with OpenAI's or Anthropic's API is a runtime setting, not a build step — the README covers it.
cd desktop
pnpm tauri devThis builds the React UI (~10s) and the Rust shell (~5–10 min the first time; seconds on subsequent runs), then opens the PDFusion window. The Tauri shell will automatically spawn the Python sidecar from the conda env in the background.
conda activate pdfusion
python main.pyThis prints READY port=<n> token=<n> and then serves the FastAPI app on
http://127.0.0.1:<n>. OpenAPI docs are at http://127.0.0.1:<n>/docs.
Same three steps on every platform; only the script extension differs.
# 1. Install Python deps + PyInstaller (not in requirements.txt — it's a dev extra).
conda activate pdfusion
pip install -r requirements.txt
pip install -e ".[dev]" # or just: pip install pyinstaller
# 2. (Optional but recommended) Stage the ~290 MB of engine assets the
# installer ships, so it is offline-ready. Without this the app downloads
# them on the user's first translate instead.
./fetch-offline-assets.sh # Windows: ./fetch-offline-assets.ps1
# 3. Install frontend deps and build.
cd desktop
pnpm install
pnpm tauri buildThe Tauri bundler auto-runs the build-sidecar script (via the
beforeBundleCommand in each tauri.<platform>.conf.json), which invokes
PyInstaller against pdfusion-sidecar.spec and then stages the result. Where
it stages it differs, because PyInstaller's one-dir bootloader requires
_internal/ to sit next to the executable and the bundlers put binaries and
resources in different places:
| Staged to | Shipped as | |
|---|---|---|
| Windows | desktop/src-tauri/binaries/ + desktop/src-tauri/_internal/ |
externalBin + a resources glob, both at the install root |
| Linux, macOS | desktop/src-tauri/sidecar/ (the whole tree) |
one resources directory |
Output:
# Windows
desktop/src-tauri/target/release/bundle/nsis/PDFusion_<version>_x64-setup.exe
# Linux
desktop/src-tauri/target/release/bundle/deb/PDFusion_<version>_amd64.deb
Notes:
- Windows installs per user — under
%LOCALAPPDATA%\Programs\PDFusion, with no admin rights and no UAC prompt. NSIS is the only Windows bundle target; the per-machine WiX.msiwas dropped, so re-add"msi"tobundle.targetsintauri.windows.conf.jsonif you need one for an IT deployment. - The
.debdepends onlibwebkit2gtk-4.1-0andlibgtk-3-0, derived by the bundler rather than hand-listed, and takes the webview from the system. There is no AppImage — see Why there is no AppImage in the packaging notes. - First build is slow — ~10–20 min, because PyInstaller bundles the full chromadb + babeldoc stack.
- The bundles are large. v1.1.1 shipped a 458 MB
.exeand a 723 MB.deb; the staged sidecar tree alone is ~1.2 GB before compression. Most of the rest is the ~290 MB of engine assets it ships so the app works offline on first run. The RAG embedding weights (118 MB) are not in there — they download lazily on first Chat use to~/.cache/huggingface. - Installers are unsigned by default — Windows SmartScreen warns on first install; the Linux bundles carry no signature either. See Code signing below.
- Dev iteration without a full PyInstaller build: if you only want to
hack on the React/Rust side and don't need a working bundled sidecar,
run
./build-sidecar.sh --stub(Windows:./build-sidecar.ps1 -Stub) once to drop placeholder files sopnpm tauri devandcargo checksucceed. The dev shell falls back to your local conda Python at runtime.
tauri.conf.json carries the digestAlgorithm and timestampUrl half of the
configuration; what it deliberately does not carry is a certificate. Supply one
of the two and the release workflow signs:
- Azure Trusted Signing (no cert to store, billed per month) — set the
repository secret
WINDOWS_SIGN_COMMANDto the signing invocation, with%1standing in for the file being signed..github/workflows/release.ymlpasses it through tobundle.windows.signCommand. - An OV/EV certificate in the runner's store — set
bundle.windows.certificateThumbprintinstead.
With neither set the workflow prints a warning and produces an unsigned installer, which is the current shipped state. SmartScreen keeps warning until one of them is configured.
PDFusion/
├── desktop/ ← Tauri + React frontend
│ ├── src/ ← React + TypeScript
│ │ ├── components/ ← UI components (shadcn-based)
│ │ ├── hooks/ ← TanStack Query + custom hooks
│ │ └── lib/ ← API client, SSE, Zustand store
│ └── src-tauri/ ← Rust shell, sidecar lifecycle, icons
├── src/desktop_pdf_translator/
│ ├── api/ ← FastAPI sidecar
│ ├── config/ ← TOML + .env settings
│ ├── processors/ ← BabelDOC translation pipeline
│ ├── translators/ ← Argos (offline) / OpenAI / Gemini / Anthropic
│ ├── rag/ ← ChromaDB + RAG chain + hybrid retrieval
│ ├── storage/ ← SQLite records and migrations
│ └── utils/ ← API key encryption, paths, logging
├── scripts/ ← build_sidecar, fetch_offline_assets, render_icon
├── assets/branding/ ← icon source of truth (see render_icon.py)
├── docs/ ← this file + architecture/ (the why, per area)
├── pdfusion-sidecar.spec ← PyInstaller spec
├── main.py ← Standalone sidecar runner
└── requirements.txt
app.log (Python sidecar) and shell.log (Rust shell) are written to logs/
under the data root for your platform — see the table in
Platform support. The boot screen's Show logs folder
button opens it, whichever one that is.