Run the browser app
Start the Bun server and chat with brains in the UI.
The browser app is the CLI's longer-lived counterpart. It keeps brains around, supports several at once, and gives you a chat thread per brain.
Start the server
PORT=4180 bun run devYou should see:
LocalLens is running at http://localhost:4180Without PORT, the server listens on 3000. Open
http://localhost:4180 in any modern browser.
Walk through the demo
-
Enter a brain name. Something like
Sample brain. -
Click Choose folder. The browser file picker opens. Select
examples/sample-brainfrom the repo. -
Click Create and index. The UI streams progress while files are read, chunked, and embedded.
-
Once the brain shows up as
indexedin the sidebar, type a question into the chat input. For example:Why does LocalLens use QWEN3_1_7B_INST_Q4?
-
Hit Send. The answer streams in with
[1],[2]citations, and the sources list at the bottom links each one back to the file.
What's behind the UI
The browser app is plain HTML, JS, and CSS in src/ui/. It talks
to the Bun server in src/server.ts over a small JSON API:
| Method | Path | Body | Returns |
|---|---|---|---|
GET | /api/health | — | { ok: true, name: "LocalLens" } |
GET | /api/brains | — | { brains: Brain[] } |
POST | /api/brains/from-files | CreateBrainFromFilesInput | { brain: Brain } |
POST | /api/brains/:id/chat | { question: string } | ChatAnswer |
DELETE | /api/brains/:id | — | 204 No Content |
Every route lands in LocalLensApp in src/locallens.ts.
Picking a folder in the browser
The <input type="file" webkitdirectory> API gives the browser the
folder's contents but not its absolute path. LocalLens stores the
brain with a virtual path like browser://my-folder so you can
still tell where it came from.
Stopping the server
Ctrl+C triggers the SIGINT handler in src/server.ts. It calls
app.close() and tears QVAC down cleanly:
process.on("SIGINT", async () => {
await app.close();
process.exit(0);
});