feat(desktop): package OpenKB as a Tauri desktop app (draft)#192
Draft
KylinMountain wants to merge 13 commits into
Draft
feat(desktop): package OpenKB as a Tauri desktop app (draft)#192KylinMountain wants to merge 13 commits into
KylinMountain wants to merge 13 commits into
Conversation
Foundation for packaging OpenKB as a desktop app for non-technical users. The architecture is a thin Tauri (Rust) shell that spawns the openkb-api server (from #150) frozen into a slim, self-contained PyInstaller sidecar, then points the system WebView at it over localhost. desktop/packaging/ — freezes openkb.api:main into a slim onedir binary: - pyi_rthook_magika.py: runtime hook stubbing magika so markitdown converts Office/HTML by file extension, letting the build exclude magika + onnxruntime entirely. - prune_litellm_proxy.py: post-build removal of the unused LiteLLM Proxy Server (static/data assets no client code path imports). - build_sidecar.sh: the PyInstaller recipe wiring both together. - sidecar_entry.py: frozen entry mirroring the openkb-api console script. desktop/src-tauri/ — reference Tauri v2 shell: spawns the sidecar, shows a splash while polling its health, navigates the WebView to it, kills it on exit. PyInstaller onedir output is bundled as a resource directory (not externalBin, which expects a single file). Verified (x86_64 Linux): the frozen slim sidecar boots uvicorn and serves GET /api/v1/kbs -> 200; ≈134 MB compressed / 338 MB on disk. The src-tauri shell is a skeleton, not compiled here (the dev container lacks webkit2gtk); build it where the platform WebView libraries are present. See desktop/README.md. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
…icons Verified by installing libwebkit2gtk-4.1-dev and running `cargo build`, which compiled the full Tauri dependency tree and surfaced two skeleton errors: - `frontendDist` pointed at "../splash" (resolves outside src-tauri) but the splash page lives at src-tauri/splash — corrected to "splash". - `generate_context!` requires a default `icons/icon.png`; added a generated app icon set (png sizes + ico). `cargo build` now finishes cleanly. Running the window still needs a display (headless container has none); the web UI itself was verified rendering end-to-end against the sidecar via headless Chromium. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
…under xvfb If the sidecar can't start (missing binary, port already in use), log and keep the window on the splash instead of aborting the whole app — better failure UX, and it lets `cargo run` dev attach to an externally-run `openkb-api`. Verified end-to-end: built the web UI, ran openkb-api on :8765, launched the Tauri binary under xvfb (WebKitGTK). The shell polled the server, navigated off the splash, and rendered the real Workbench UI — confirming the full shell → poll → navigate → render flow works. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
Re-implements the setup wizard natively in the new (#189) React/TS frontend, using its conventions — react-i18next (common namespace, en+zh parity), shadcn Dialog/Button, and the config/kb API clients. Shown when the global config reports no LLM key (has_api_key=false): 1. Welcome. 2. Connect your model — model + API key + optional custom base URL, saved via patchGlobalConfig (RFC 7386 merge-patch; the key is only sent when typed and is never returned by the API). 3. Create your first knowledge base — createKb, then navigates to it. "Skip for now" dismisses it. Wired in App.tsx via a getGlobalConfig() check on mount. Build passes (i18n:check key-parity + no-hardcoded-CJK guard, tsc -b, vite build); verified rendering steps 1–2 via headless Chromium against a freshly-built frontend served by openkb-api. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
The #189 frontend gates desktop-only UI (the in-app TitleBar, offset chrome cluster) on a `window.__OPENKB_DESKTOP__` flag. Set it from the shell so those features light up when the UI runs inside Tauri. The window is now created in setup() via WebviewWindowBuilder with an initialization_script, instead of declaratively in tauri.conf.json — an init script runs before page scripts on every navigation, so the flag is present on the splash and (critically) after we navigate the WebView to the sidecar's served UI. tauri.conf.json's windows[] is emptied accordingly. Verified under xvfb (WebKitGTK): with the sidecar serving the new frontend, the Tauri window renders the desktop TitleBar ("OpenKB Studio — Home", traffic lights, Local badge) — confirming the flag reaches the page. cargo build clean. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
…ecar The sidecar froze only openkb's Python modules, so `openkb.api`'s static mount of `openkb/web` found nothing and served `/` as 404 — an installed app would open to a blank/error page. Add `openkb` to `--collect-all` so PyInstaller bundles its data files (the built web UI at openkb/web, plus prompt/template assets) and any lazy/dynamically-imported submodules. The web UI must be built (npm run build → openkb/web) before this script runs. Verified: the frozen sidecar now serves `/` → 200 with `<title>OpenKB Studio</title>` and its `/assets/*.js` → 200 (was 404). Size 336M → 343M. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
New workflow that builds the desktop app: install WebView deps → build the web UI → freeze the openkb-api sidecar (PyInstaller) → `cargo tauri build` → upload the installers. Triggered on workflow_dispatch and `desktop-v*` tags. Linux-only for now; the matrix is structured so Windows and macOS (arm64 + intel) entries can be added once the Linux path is proven — each needs its own sidecar freeze since neither PyInstaller nor Tauri cross-compiles. Reuses the existing workflows' pinned action SHAs (checkout/setup-python/ setup-node); upload-artifact is left on a tag with a TODO to SHA-pin. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
The pipeline (web build → sidecar freeze bundling openkb/web → cargo tauri build) is verified locally end to end: it produces a .deb and .rpm (~175 MB each) containing the app + frozen sidecar, and the shell renders the desktop TitleBar under xvfb. Notes the AppImage target needs network for appimagetool. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
Extends the desktop build to all platforms (each freezes its own sidecar and links its own WebView — neither cross-compiles) and adds a release job that attaches every platform's installers to a GitHub Release on a `desktop-v*` tag. The sidecar freeze runs under `shell: bash` (git-bash on Windows) with PYTHON=python. macOS arm64/x86_64 are native builds on macos-14/macos-13. Artifacts are unsigned for now (macOS notarization / Windows signing TBD); a couple of first-party actions still need SHA-pinning (marked TODO). The Linux path is verified locally (.deb/.rpm); Windows/macOS need a real runner. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
Adds a "Wiki language" field to the onboarding setup step, reusing the existing UnLanguageDatalist (the six official UN languages) as free-text quick-picks — so a non-English user sets their knowledge-base language up front. Saved via patchGlobalConfig config.language. en/zh label added to the common namespace.
Expands the interface beyond zh/en to the remaining UN official languages
(minus Arabic for now — RTL is a separate effort):
- SUPPORTED_LANGUAGES gains fr/es/ru; the LanguageToggle becomes a dropdown
menu (radio group of every language by its autonym) instead of a 2-way
toggle, and initial-language detection matches the browser locale's base
subtag against the supported set.
- Full fr/es/ru translations for all eight namespaces (common, home, kbList,
kb, kbSettings, settings, chat, artifacts), preserving every {{placeholder}}
and <0/> tag; Russian includes the one/few/many/other plural forms. Each
locale has complete key coverage vs en (no zh fallback at runtime).
Verified: build passes (i18n:check en/zh parity + no-hardcoded-CJK, tsc, vite);
the menu lists 中文/English/Français/Español/Русский and switching to Français
translates the whole UI (headless Chromium).
Note: fr/es/ru strings are machine translations and should get a native-speaker
review pass before release.
Adds Japanese to SUPPORTED_LANGUAGES and full translations for all 8 namespaces (common, home, kbList, kb, kbSettings, settings, chat, artifacts). Japanese has no plural distinction, so _one mirrors _other. Registers 日本語 in the language dropdown (en/zh common.json autonyms). Verified: full key parity vs en, build passes, UI renders in Japanese. Claude-Session: https://claude.ai/code/session_01KZyUSGAzVL9yxpsWWPv6Y2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Packages OpenKB as a desktop app for non-technical users — no
pip, no terminal.A thin Tauri (Rust) shell wraps two things the repo already produces:
openkb-apifrozen into a self-contained, slimmedPyInstaller binary that serves both the JSON/SSE API and the built web UI.
WebView at it.
The user double-clicks the app; the shell starts the sidecar on localhost, waits
until it answers, then navigates the WebView to it. No browser, no port, no
"server" ever surfaces.
What's in it
desktop/packaging/— freezeopenkb.apiinto a slim sidecar:pyi_rthook_magika.py: runtime hook stubbingmagikasomarkitdownconvertsOffice/HTML by file extension, letting the build drop
magika+onnxruntime.prune_litellm_proxy.py: post-build removal of the unused LiteLLM proxy server.build_sidecar.sh: the PyInstaller recipe;--collect-all openkbbundles thebuilt web UI (
openkb/web) + prompt/template data so the sidecar serves/.desktop/src-tauri/— the Tauri v2 shell: creates the window with aninit-script that sets
window.__OPENKB_DESKTOP__(the Studio frontend gates itsin-app TitleBar on it), spawns the sidecar (non-fatal on failure), polls its
health, navigates to it, and kills it on exit. The PyInstaller onedir is bundled
as a resource dir (not
externalBin, which expects a single file).frontend/— a first-launch onboarding wizard on the Studio frontend(welcome → connect model → create first KB), shown when no API key is configured.
Built with the frontend's own conventions (react-i18next en/zh, shadcn Dialog,
config/kbAPI clients); no new backend..github/workflows/desktop.yml— build matrix overLinux / Windows / macOS(arm64 + x86_64), and a
releasejob that attaches everyplatform's installers to a GitHub Release on a
desktop-v*tag.Verified (locally, x86_64 Linux)
GET /api/v1/kbsand the UIat
/(200). ~134 MB compressed; nomagika/onnxruntime, proxy pruned.xvfb(WebKitGTK): it navigates to thesidecar and renders the desktop TitleBar (confirming
__OPENKB_DESKTOP__reaches the page).
Studio config API.
cargo tauri buildproduces a.deband.rpm(~175 MB each) containingthe app + bundled sidecar.
Not done yet / needs attention
runners; not yet exercised.
SmartScreen will warn). Needs certificates before public distribution.
appimagetoolat build time (needs unrestrictednetwork; fine on GitHub runners).
upload/download-artifact) still need SHA-pinning tomatch the repo's policy (marked
TODOin the workflow).pyinstalleris installed ad-hoc in CI (build tool, not a runtime dep).Scope
Additive only — everything lives under
desktop/plus the frontend onboarding.No changes to existing Python modules or the packaged wheel.
Generated by Claude Code