A friendly, privacy-first AI chat assistant, hand-drawn sketchbook style, that runs entirely in your browser.
No server Β· No account Β· No tracking Β· Bring your own OpenRouter API key Β· installable as an app Β· no build step, no framework.
- β¨ Features
- π Why it's safe to paste in an API key
- π Installation and usage
- π Code structure
- π οΈ Tech stack
β οΈ Known limitations
π¬ Streaming chat. Full Markdown rendering β headings, code blocks, lists, links β all sanitized before it ever touches the page.
π Bring your own key. Paste an OpenRouter API key in Settings and you're chatting; the key never leaves your browser except to call OpenRouter directly.
πΎ Local persistence. Chat history and settings are saved in
localStorage, so your conversation survives a refresh.
ποΈ Voice input. Dictate messages via the Web Speech API on Chromium-based browsers.
π Copy & retry. One-click actions on any message, plus swipe-to-copy on touch devices.
π΄ Installable PWA. Add it to your home screen and the app shell keeps working offline.
βΏ Accessible. Every interactive element is a real, focusable, labeled control β built for keyboard and screen-reader use from the start.
π Model-agnostic. Talk to GPT-4o, Claude, Gemini, Llama, DeepSeek, Qwen, or anything else available on OpenRouter, without running a backend of your own.
This is a fair question for any "bring your own key" tool, so here's the actual threat model:
- π₯οΈ 100% static. There is no backend that could see, log, or leak your key.
- π One destination. Your key lives only in this browser's
localStorageand is sent, with every request, straight tohttps://openrouter.aiβ nowhere else. - π§± Locked-down CSP. A strict Content-Security-Policy restricts
connect-srcto'self'andopenrouter.aionly, so even a hypothetical script-injection bug couldn't exfiltrate your key to a third-party server. - π Pinned dependencies. Every third-party script is pinned to an exact version with a Subresource Integrity hash, so a compromised CDN can't silently swap the code that runs.
- π§Ό Sanitized output. All model output is parsed as Markdown and sanitized with DOMPurify before render β the model can't inject scripts, images, or styling into the UI.
That said: localStorage is readable by anyone with access to this browser profile. Don't use this on a shared or public computer, and use an OpenRouter key with a spend cap you're comfortable with.
There's no build step β it's plain HTML/CSS/JS. To run it locally, serve the folder over HTTP (opening index.html directly as a file:// URL breaks the Content-Security-Policy and service worker):
# from the project folder, pick whichever you have installed
python -m http.server 8000
# or
npx serve .Then open http://localhost:8000, click Settings, and paste in an OpenRouter API key.
| Platform | Steps |
|---|---|
| Desktop (Chrome/Edge) | Click the install icon (β) in the address bar |
| Mobile (Safari/iOS) | Tap Share, scroll down, tap Add to Home Screen |
| Mobile (Chrome/Android) | Tap the three-dot menu, select Add to Home screen |
Once installed, the app shell works offline, and your chats and settings stay saved locally on your device.
AskieAI-public/
βββ index.html Markup, inline styles, CSP, manifest/meta links
βββ app.js All client-side logic: chat, storage, settings, voice, PWA install
βββ sw.js Service worker β caches the app shell for offline use
βββ .nojekyll Tells GitHub Pages to skip the Jekyll build step
βββ robots.txt
βββ sitemap.xml
βββ README.md
βββ PatrickHand-Regular.woff2 Self-hosted webfont (Patrick Hand), used everywhere
βββ askieogimage.png Open Graph / Twitter share image (1200Γ630)
βββ favicon/
βββ site.webmanifest PWA manifest (name, icons, start_url, theme colors)
βββ favicon.ico / favicon.svg / favicon-96x96.png
βββ apple-touch-icon.png
βββ web-app-manifest-{192,512}x512.png
No framework, no bundler, no node_modules β just three pinned dependencies loaded from jsDelivr with Subresource Integrity:
| Library | Purpose |
|---|---|
| marked | Parses assistant responses as Markdown |
| DOMPurify | Sanitizes the resulting HTML before render |
| OpenRouter | The actual LLM API β not a dependency in the code, but the whole point |
- ποΈ Voice input is Chromium-only. It relies on the Web Speech API, which is only implemented in Chromium-based browsers (Chrome, Edge) β Firefox and Safari users won't see it work.
- π΄ No sync. Chat history is per-browser, per-device β there's no sync, since there's no account or server to sync through.
- π Offline chat still needs a connection. The service worker caches this app's own files for offline use; it deliberately does not cache OpenRouter responses, so you still need a connection to actually chat.