Lemonade Desktop App (Tauri)
A native desktop GUI for interacting with the Lemonade Server.
Overview
This app provides a native desktop experience for managing models and chatting with LLMs running on lemond. It connects to the server via HTTP API and offers a modern, resizable panel-based interface.
It is built with Tauri v2, which embeds the operating system's native webview — WebView2 on Windows, WKWebView on macOS, and webkit2gtk on Linux — instead of bundling Chromium. The renderer is a standard React 19 + TypeScript application served by webpack and shared with the browser-only src/web-app/ build.
Key Features:
- Model management (list, pull, load/unload)
- Chat interface with markdown/code rendering and LaTeX support
- Real-time server log viewer
- Persistent layout and inference settings
- Custom frameless window with zoom controls
- lemonade:// deep-link protocol handler
- UDP beacon discovery to find a running lemond server on the local machine
Deployment Topology
The Tauri desktop app is a thin client for a separately-running lemond server. A single lemond can be driven by multiple clients at once, including clients running on other machines.
Consequences that callers of this code need to know about:
- Per-client local state. All user-tunable state (inference params, layout sizes, zoom, base URL, API key) lives in
~/.cache/lemonade/app_settings.jsonon the client, owned by the Rust host (src-tauri/src/settings.rs). It is never stored or proxied throughlemond, because two clients against the same server must be able to hold different preferences. - The desktop app does not manage
lemond's lifecycle. The server is started independently — on Windows byLemonadeServer.exe(auto-started via the startup folder, tray icon always visible), on Linux/macOS by the user or a service. The Tauri app is opened on demand and must not add itself to autostart, spawnlemondas a subprocess, or assumelemondis on the same machine. - Discovery is best-effort local + explicit remote.
beacon.rslistens for a UDP broadcast emitted by a locallemondto auto-populate the base URL. For remote-server use, the user setsbaseURL+apiKeyin settings and the client talks to that endpoint directly.
Code Structure
src/app/
├── package.json # Webpack + Tauri CLI devDependencies
├── webpack.config.js # Bundler config (target: web)
├── tsconfig.json # TypeScript config
├── assets/ # Icons, logos
│
├── src/
│ ├── global.d.ts # window.api type declaration
│ └── renderer/ # React UI (TypeScript)
│ ├── index.tsx # Renderer entry (imports tauriShim first)
│ ├── tauriShim.ts # Installs window.api → Tauri invoke() bridge
│ ├── App.tsx # Root component, layout orchestration
│ ├── TitleBar.tsx # Custom window controls
│ ├── ModelManager.tsx # Model list and actions
│ ├── ChatWindow.tsx # LLM chat interface
│ ├── LogsWindow.tsx # Server log viewer
│ ├── SettingsPanel.tsx # Inference parameters
│ └── utils/ # API helpers and config
│
└── src-tauri/ # Rust host (Tauri backend)
├── Cargo.toml # Rust dependencies
├── tauri.conf.json # Window config, bundle settings, plugins
├── build.rs # tauri_build::build()
├── capabilities/default.json # Tauri permissions
├── icons/ # Generated app icons (32x32/128x128/ico/icns)
└── src/
├── main.rs # Entry point (binary)
├── lib.rs # Tauri builder, plugin wiring, deep-link routing
├── commands.rs # #[tauri::command] handlers (window, settings, port)
├── events.rs # Tauri event channel name constants
├── settings.rs # app_settings.json read/write + sanitize
├── beacon.rs # UDP beacon listener (single bound socket)
├── tray_launcher.rs # macOS-only tray auto-start helper
└── webview_shim.rs # Per-platform webview hooks (mic permission, link interception)
/health,/system-stats, and/system-infoare NOT proxied through Rust. The renderer fetches them directly viaserverConfig.fetch(...); seeStatusBar.tsxandAboutModal.tsx.
Architecture
┌────────────────────────────────────────────────┐
│ Tauri Rust Host (src-tauri/) │
│ Window mgmt, IPC commands, background tasks │
│ UDP beacon listener, settings file I/O │
├────────────────────────────────────────────────┤
│ tauriShim.ts (installs window.api in webview) │
│ Maps window.api.* → invoke() / listen() │
├────────────────────────────────────────────────┤
│ React Renderer (TypeScript) │
│ Source lives in src/app/src/; the web-app │
│ build (src/web-app/) reuses it via webpack │
│ relative entry/template paths — no symlinks. │
├────────────────────────────────────────────────┤
│ HTTP API → lemond (C++ server) │
└────────────────────────────────────────────────┘
Prerequisites
- Node.js 20 or higher (webpack)
- Rust toolchain via rustup (Rust 1.77+)
- Linux only:
libwebkit2gtk-4.1-dev,libsoup-3.0-dev,libjavascriptcoregtk-4.1-dev,librsvg2-dev,libayatana-appindicator3-dev— the repo'ssetup.shscript checks for these and prompts to install them. - Windows only: WebView2 runtime (pre-installed on Windows 10 1803+ and Windows 11).
- macOS only: No extra dependencies — WKWebView ships with the OS.
Building
cd src/app
# Install webpack + Tauri CLI dependencies
npm ci
# Run in dev mode (opens a window, hot-reloads webpack)
npm run dev
# Production build (single binary, no OS bundles)
npm run build -- --no-bundle
# Production build with platform bundles (macOS .app, Linux .deb/.rpm, Windows MSI/NSIS)
npm run build
The preferred path for shipping is through CMake, which stages the Tauri output alongside the rest of the server:
cmake --build --preset default --target tauri-app # Linux / macOS
cmake --build --preset windows --target tauri-app # Windows
Development Scripts
npm run dev # Tauri dev mode (window + hot-reload)
npm run build # Tauri production build
npm run tauri icon <path> # Regenerate icons from a source image
npm run build:renderer # Build just the renderer (webpack, dev mode)
npm run build:renderer:prod # Build just the renderer (webpack, production)
npm run watch:renderer # Webpack watch mode for the renderer only
Testing the Rust host
Unit tests live alongside the Rust modules and cover settings sanitization, beacon parsing, and deep-link URL parsing:
cargo test --manifest-path src-tauri/Cargo.toml