Build reliable OpenAI-compatible endpoints by arranging AI models visually.
Routing, made visible. The vault on the left holds the models you've kept; the endpoints on the right are live OpenAI-compatible servers. The relic at the right edge of each lane answers first — everything to its left is a fallback, tried in order.
VisualLLM is a visual fallback router for AI models. It's a desktop application that lets you:
- Add your AI providers (OpenRouter, OpenAI, Anthropic, or any OpenAI-compatible endpoint)
- Browse their model catalogs with rich filtering and sorting
- Drag models from the vault (left sidebar) into any lane — each lane is an ordered fallback chain
- Reorder models within a lane by dragging — the rightmost model answers first; everything to its left is a fallback
- Expose lanes as local OpenAI-compatible endpoints that your tools can connect to
- Integrate a lane into your editor (VS Code, VS Code Insiders, Windsurf) with one click — it shows up in the editor's own model picker
- Self-update from GitHub — the app checks for a new signed release at startup and offers to restart when one is ready
When a request comes in, the rightmost model answers first. If it fails, can't serve the request, or returns unusable content, VisualLLM automatically tries the next model in line — and explains exactly what happened at each step.
The interface is an installation: a living, reaction-diffusion field rendered on the GPU (with an honest CPU fallback), slabs of smoked acrylic floating above it, and a lane's traffic rippling through the chemistry at its own position. Nothing is a static rectangle.
VisualLLM is a desktop GUI that configures and runs a local LLM proxy. Under the hood:
- The Rust backend runs a lightweight HTTP server on
127.0.0.1:4100— this is the proxy. - Each lane you create becomes a distinct OpenAI-compatible endpoint at
/lane/<slug>/v1. - The GUI lets you add providers, browse their catalogs, and drag models into lanes — no YAML, no code.
- At request time, the proxy walks the lane right-to-left, trying each model until one succeeds (capability check → request → commit gate → forward).
- Every hop is recorded — you get headers (
x-visualllm-served-by,x-visualllm-passed-over,x-visualllm-trail) and a full incident log.
Think of it as: a visual control plane for a multi-provider LLM proxy. You configure the routing visually; the proxy executes it faithfully.
Most AI gateway solutions make routing a configuration problem — editing YAML files, managing complex rules, or writing code. VisualLLM makes it visible and intuitive: you see your models, you arrange them in order, and the system does exactly what you expect.
This is especially valuable when:
- You want automatic fallback when a provider is rate-limited, out of credit, or unavailable
- You need reliable responses and want to understand exactly which model served each request
- You use multiple providers and want to route requests based on capability, not just availability
- You want transparency — knowing not just that a request succeeded, but which model answered and why
Everything is arranged visually. No configuration files to edit, no YAML to learn. What you see is what you get.
- API keys never leave the Rust backend — the webview has no network or filesystem access
- Keys are stored in the OS keychain (Linux native), not in plaintext files
- All network requests flow through Rust — the renderer only receives state, never credentials
- Bound to loopback (127.0.0.1) — your endpoints are local-only by default
- Capability checking — models that can't serve your request are skipped, not tried
- Commit gate — 200 responses are verified to contain usable content before forwarding
- Loop detection — optional Loopwatch catches agents stuck in tool-call loops
- Detailed receipts — every failure is recorded with evidence for debugging
A response isn't successful just because a provider returned HTTP 200. VisualLLM verifies that:
- The response contains actual content (not just reasoning tokens)
- The model can actually serve the request (vision, tools, context size)
- The stream hasn't stalled or died mid-response
Every response includes headers telling you:
x-visualllm-served-by— which model actually answeredx-visualllm-passed-over— how many models were skipped or failedx-visualllm-trail— the complete story of what happened
- OpenAI-compatible — works with VS Code, Cursor, and any OpenAI-compatible client
- No build step — the renderer is plain HTML/CSS/JS
- Rust backend — fast, safe, and reliable
- Tauri framework — lightweight, secure desktop app
Ready-to-run installers are published to GitHub Releases:
.debfor Debian/Ubuntu- AppImage for any Linux distribution
.msi/.exefor Windows.dmgfor macOS
VisualLLM updates itself. A few seconds after launch it checks GitHub for a newer release, downloads it in the background, and offers to restart — every download is signature-verified against the key embedded in the app. No manual reinstall needed.
We're actively looking for testers on all platforms! VisualLLM is Linux-first, but we want to ensure it works well on macOS and Windows too. If you can help test:
- macOS — Test the
.dmginstaller, notarization, and code signing - Windows — Test the
.msiand.exeinstallers, code signing, and WebView2 integration - Linux — Test
.deb, AppImage, and Flatpak on various distributions
How to help:
- Download the latest release artifacts from GitHub Releases
- Install and run VisualLLM on your platform
- Report any issues at GitHub Issues with:
- Your OS and version
- The artifact you tested (.dmg, .msi, .exe, .deb, AppImage, Flatpak)
- Steps to reproduce any problems
- Screenshots if applicable
Your feedback directly shapes the quality of the first public release!
Linux (Ubuntu/Debian):
# System dependencies for WebKit and Tauri
sudo apt update && sudo apt install -y \
libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev \
build-essential curl wget file libssl-dev
# Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# Node.js (for build tooling)
nvm install --lts # or: sudo apt install nodejs npmOther distributions: See detailed instructions below.
# Clone the repository
cd /path/to/visualllm
# Install dependencies
npm ci
# Verify everything works
node tools/smoke.js
cargo test --manifest-path src-tauri/Cargo.toml
# Run the app in development mode
npm run dev
# Build release packages (.deb + AppImage on Linux)
npm run build
# Or run the compiled binary outside a snap-polluted terminal
# (prefers target/release if it exists, otherwise target/debug)
tools/launch-system.shThe app will open a window and start the engine on http://127.0.0.1:4100.
Click Providers in the sidebar, then Add Provider. Enter:
- Name — a friendly name for this provider
- Kind —
openrouter,openai,anthropic, orgeneric - Base URL — the API endpoint (defaults based on kind)
- API Key — your secret key (stored securely in the OS keychain)
Click Browse to see all models from your configured providers. Use the filters and sorting options to find what you need:
- Filter by capability (vision, tools, reasoning)
- Sort by intelligence, coding, agentic scores (OpenRouter)
- Filter by author, context size, price
Click Pool to select models you're interested in. Then:
- Click New Lane
- Drag models from the pool into your lane (right to left = primary to fallback)
- Give it a name and save
The lane is now a local OpenAI-compatible endpoint at:
http://127.0.0.1:4100/lane/<lane-slug>/v1
VS Code / Cursor / Any OpenAI-compatible client:
- Base URL:
http://127.0.0.1:4100/lane/<lane-slug>/v1 - Model:
<lane-slug>(the same as your lane name) - API Key: Leave blank (or use any placeholder if required)
Direct cURL request:
curl http://127.0.0.1:4100/lane/my-lane/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "my-lane",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 50
}'Instead of wiring endpoints by hand, add a lane straight into an editor's model picker with one click. On each lane, open the editor picker and choose a target:
- VS Code — the lane appears as a
customendpointmodel you can select in Copilot Chat / the model picker - VS Code Insiders — same integration for the Insiders build
- Windsurf — same VS Code schema,
Windsurfconfig directory
VisualLLM writes the lane into the editor's chatLanguageModels.json, derives
its capabilities (vision, tools, context window) from the lane's actual members,
and removes it automatically when the lane is deleted. Re-running an
integration updates the existing entry rather than duplicating it.
Each model in a lane can have its own parameters:
- Temperature, top-p, frequency/presence/repetition penalties
- Max tokens limit
- These override the client's request for that specific model
Enable "No thinking" on a lane to ask providers (that support it) to skip reasoning tokens. This is a preference, not a guarantee — the commit gate catches models that think anyway.
Enable "Unstick" on a lane to detect and break tool-call loops:
- Repeat loops: Same tool called with same arguments multiple times
- Futile loops: Different arguments returning identical results
- The loop is collapsed and a diagnostic note is appended to the conversation
Every failure is recorded with:
- Timestamp and which lane/member failed
- The failure kind (rate_limited, out_of_credit, capability_gap, etc.)
- The evidence (provider error messages, trail notes)
- Lane settings at the time (no_think, loopwatch enabled)
Click the bell icon in the status bar to view recent incidents.
Your lanes, pool, and provider configuration can move between machines:
- Export — open the Providers panel and click Export…. This saves a JSON file containing lanes, pool, and provider settings. API keys are never included; they stay in your OS keychain.
- Import — click Import… and choose a previously exported file.
- Merge — combines the file with your current state. Existing providers keep their local API keys, and lanes with the same slug are updated rather than duplicated.
- Replace — wipes your current lanes, pool, and providers and loads the file exactly. You will need to re-enter API keys afterward.
VisualLLM follows a strict security architecture:
┌─────────────────────────────────────────────────────────┐
│ TAURI APPLICATION │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ WEBVIEW │ │ RUST BACKEND │ │
│ │ (Renderer) │ │ (src-tauri/src/) │ │
│ │ │ │ │ │
│ │ • HTML/CSS/JS │ │ • Provider key storage │ │
│ │ • No network │◄─────┤ • Catalog fetching │ │
│ │ • No filesystem │ │ • Request routing │ │
│ │ • Tauri commands│ │ • Fallback logic │ │
│ │ │ │ • Loop detection │ │
│ │ │ │ • Incident recording │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ OS Keychain │ ← API keys stored here
└─────────────────┘
The webview can only:
- Render the UI
- Call Tauri commands defined in
src-tauri/src/main.rs - Receive state from the Rust backend
The webview CANNOT:
- Make HTTP requests
- Access the filesystem
- Read API keys
- Execute arbitrary code
visualllm/
├── renderer/ # Frontend (HTML/CSS/JS)
│ ├── index.html # Main window
│ ├── style.css # Styles
│ └── app.js # All UI logic
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── main.rs # Tauri shell, commands
│ │ ├── server.rs # HTTP engine, routing
│ │ ├── providers.rs # Provider management, catalogs
│ │ ├── lanes.rs # Lane storage and management
│ │ ├── incidents.rs # Failure recording
│ │ └── loopwatch.rs # Loop detection
│ ├── Cargo.toml # Rust dependencies
│ └── tauri.conf.json # Tauri configuration
├── tools/ # Development scripts
│ ├── smoke.js # Renderer smoke test
│ └── preview.js # Browser preview harness
├── README.md # This file
├── ROADMAP.md # Public release plan
├── CONTRIBUTING.md # Development guidelines
├── SECURITY.md # Security policy
└── LICENSE # MIT License
Client Request
│
▼
┌─────────────────────┐
│ Engine (server.rs) │ ← Listens on 127.0.0.1:4100
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Find Lane │ ← Looks up lane by slug
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Inspect Request │ ← Checks vision, tools, token needs
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Walk Members │ ← Tries each model in order
└─────────────────────┘
│
├─ Can this model serve? (capability check)
│
▼
┌─────────────────────┐
│ Send to Provider │ ← With member-specific params
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Verify Response │ ← Commit gate: has usable content?
└─────────────────────┘
│
├─ If usable: Forward to client with headers
│
▼
┌─────────────────────┐
│ Try Next Member │ ← If failed or unusable
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Return Error │ ← If all members failed
└─────────────────────┘
# Install system dependencies
sudo apt update && sudo apt install -y \
libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev \
build-essential curl wget file libssl-dev libgtk-3-dev
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
rustup default stable
# Install Node.js (via nvm recommended)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --ltssudo dnf install -y \
webkit2gtk4.1-devel libxdo-devel libayatana-appindicator-gtk3-devel librsvg2-devel \
gcc gcc-c++ make openssl-devel gtk3-devel
# Then install Rust and Node.js as abovesudo pacman -S \
webkit2gtk-4.1 libxdo libayatana-appindicator librsvg base-devel openssl gtk3
# Then install Rust and Node.js as aboveFor reliable launching on Linux (especially under Wayland or in snap environments):
# From the repo root
export PATH="$HOME/.cargo/bin:$PATH"
env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:$HOME/.cargo/bin" \
DISPLAY="${DISPLAY:-:0}" \
XAUTHORITY="${XAUTHORITY:-$HOME/.Xauthority}" \
./src-tauri/target/debug/visualllmWhy this works:
env -istarts with a clean environment (no snap library conflicts)- Explicitly sets the paths needed for GTK/WebKit
- Works with both X11 and Wayland
| Issue | Solution |
|---|---|
symbol lookup error: libpthread |
You're inheriting snap's LD_LIBRARY_PATH. Use the env -i launch command above. |
Failed to initialize GTK |
Missing DISPLAY or XAUTHORITY. Ensure you're on a graphical session. |
Address already in use (port 4100) |
A previous instance is still running: pkill -f visualllm && fuser -k 4100/tcp |
| Window opens but shows "Could not connect" | The engine didn't start. Check terminal for errors, usually a stale process on 4100. |
| Transparent window shows black/garbled | Your compositor doesn't support ARGB visuals. Set "transparent": false in src-tauri/tauri.conf.json and rebuild. |
# Rust tests (engine logic)
cargo test --manifest-path src-tauri/Cargo.toml
# Renderer smoke test
node tools/smoke.js
# Full build
npm run buildrenderer/app.js— All UI logic, no frameworksrc-tauri/src/main.rs— Tauri shell and command definitionssrc-tauri/src/server.rs— HTTP engine and routing logicsrc-tauri/src/providers.rs— Provider management and catalog fetchingsrc-tauri/src/lanes.rs— Lane storage and member managementsrc-tauri/src/incidents.rs— Failure recording and classificationsrc-tauri/src/loopwatch.rs— Tool-call loop detection
- For UI changes: Edit files in
renderer/ - For backend logic: Edit files in
src-tauri/src/ - For new Tauri commands: Add to
main.rswith#[tauri::command] - For new HTTP routes: Add to
server.rsrouter
All new commands must be added to the capabilities list in src-tauri/capabilities/default.json.
- Visibility — You should be able to understand routing decisions at a glance
- Honesty — A response is only successful if it contains usable content
- Safety — API keys never leave the Rust backend
- Simplicity — The simple path should stay simple
- Compatibility — Existing lanes and clients should keep working
Why Tauri? Lightweight, secure, and Rust-based. It gives us a real desktop app without the overhead of Electron.
Why no framework in the renderer? The UI is small and focused. Vanilla JS keeps it fast, auditable, and dependency-free.
Why Rust for the backend? Memory safety, performance, and excellent HTTP/async support. Plus, it's where the keys live.
Why loopback-only by default? Security. Your API keys are valuable, and we don't want to accidentally expose them to your network.
We welcome contributions! Please see CONTRIBUTING.md for:
- Development setup
- Pull request guidelines
- Code style expectations
- Testing requirements
Please see SECURITY.md for:
- Our threat model
- Vulnerability reporting process
- Security design decisions
VisualLLM is licensed under the MIT License.
The public release plan, milestones, and criteria are documented in ROADMAP.md.
Before tagging a release:
- Run the full test suite:
node tools/smoke.js cargo test --manifest-path src-tauri/Cargo.toml cargo fmt --check --manifest-path src-tauri/Cargo.toml - Build release packages:
npm run build
- Verify the built binary starts cleanly and serves lanes on
127.0.0.1:4100. - Launch a second instance and confirm it detects the first rather than spawning a duplicate engine window.
- Run the AppImage on a clean Linux VM with no WebKitGTK development packages installed to confirm it bundles its dependencies.
- Update
CHANGELOG.mdwith the version, date, and summary of changes. - Bump the version to match in
src-tauri/Cargo.toml,src-tauri/tauri.conf.json, andpackage.json— the CI release pipeline refuses a tag whose version doesn't match the source. - Tag the release (
git tag vX.Y.Z && git push origin vX.Y.Z). The pipeline builds and signs the.deb, AppImage,.msi,.exe, and.dmg, uploads their.sigfiles and a freshlatest.json, and publishes the GitHub Release — which is also what the in-app auto-updater checks against.
Releases follow Semantic Versioning:
- MAJOR — incompatible changes to lane persistence, provider config, or the OpenAI-compatible endpoint contract.
- MINOR — new user-facing features, new provider kinds, or significant UI improvements.
- PATCH — bug fixes, documentation updates, and minor robustness improvements.
The first public release will be 1.0.0 once the release criteria in
ROADMAP.md are met.
VisualLLM is built by Creative Systems Development.
Built with ❤️ using Rust, Tauri, and a commitment to making AI routing visible and reliable.



