Problem
Browser-based chat clients scatter conversation data, credentials, provider traffic, and usage metrics across separate services. Relay needed to bring them together without routing secrets through the web interface.
A local-first desktop workspace for chatting with and comparing OpenAI-compatible models.

Relay is a local-first desktop workspace for chatting with and comparing OpenAI-compatible language models. It uses a React/Vite renderer inside Tauri, a Rust backend that owns provider traffic, SQLite for durable history and metrics, and the operating system credential vault for API keys.
Browser-based chat clients scatter conversation data, credentials, provider traffic, and usage metrics across separate services. Relay needed to bring them together without routing secrets through the web interface.
Relay keeps the interface thin and moves model communication, storage, and secret handling into a native Rust backend. The WebView renders state and never receives an API key or makes a provider request directly.
Real SSE streaming from the Rust backend, with stop-generation support.
Concurrent side-by-side model comparison where one column cannot abort another.
Local conversations, messages, generation attempts, and usage telemetry in SQLite.
Configurable per-million-token pricing with estimated cost reporting.
API keys stored in the OS credential vault rather than SQLite or browser storage.
Import and export through native file dialogs, with backups that exclude credentials.
HTTPS-only provider endpoints with redirect denial and private-address blocking.
SQLite was chosen for a single-user desktop app: it removes an external service, gives atomic local transactions, works offline, and is easy to back up.
Generation attempts are stored separately from assistant messages so that interruption and retry do not corrupt the logical transcript.
Writes before and after a stream use short transactions, so no database lock is held across provider network time.
Credentials live in the platform vault because encrypting a key beside its own encryption key offers little protection.
Provider networking is confined to Rust; typed wrappers in the renderer are the only backend contract.
DNS rebinding protection is best-effort because hostname resolution and connection are separate operations with the current HTTP client. A hardened release would pin the validated address for the request.
Content to be added.