Features & deeper docs
May 4, 2026 ยท View on GitHub
The bits that don't belong on the README front door.
Feature list
- ๐ฌ Floating "Ask the docs" bubble on every shared note page
- ๐ Works with any OpenAI-compatible API (Gemini, OpenAI, OpenRouter, DeepSeek, Groq, vLLM, llama.cpp, Ollama with
--api, โฆ) - ๐ Tool-using agent โ the AI calls
search_notes(query)andread_note(noteId)so only relevant content enters the context - โ Settings panel with live "Test connection" probe
- ๐ Fully editable system prompt (with a Reset to default button)
- ๐ Retry button on each answer
- ๐ฅ Fullscreen mode
- ๐ฌ Debug toggle โ logs every LLM request, response, and tool call to the browser console
- ๐ Show/hide tool-call breadcrumbs in the chat
- ๐ก Shadow DOM isolated โ your docs CSS can't bleed into the widget, and vice versa
- ๐จ Inline Feather SVG icons (no external CDN)
- ๐พ Conversation history persisted in
localStorage - ๐ Per-visitor API keys โ each user enters their own; no server-side key, no shared cost
Configuration UI
Open the chat โ click the gear icon. All settings live in the visitor's localStorage.
| Field | What it does |
|---|---|
| Base URL | Your provider's OpenAI-compatible endpoint |
| API key | Bearer token, sent only to that base URL |
| Model | Model name string for that provider |
| System prompt | Fully editable. Reset to default restores it. |
| Show tool calls in chat | Toggle the โบ Searching: "..." / โบ Reading: ... breadcrumbs |
| Debug mode | Log every LLM request/response/tool call to console |
| Test button | Pings /chat/completions with "ping" to verify base + key + model |
Provider examples
| Provider | Base URL | Sample model |
|---|---|---|
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai | gemini-2.5-flash |
| OpenAI | https://api.openai.com/v1 | gpt-4o-mini |
| OpenRouter | https://openrouter.ai/api/v1 | anthropic/claude-sonnet-4-5, google/gemini-2.5-pro, โฆ |
| DeepSeek | https://api.deepseek.com | deepseek-chat, deepseek-reasoner |
| Groq | https://api.groq.com/openai/v1 | llama-3.3-70b-versatile |
| Local Ollama | http://localhost:11434/v1 | whatever you've pulled |
| Self-hosted vLLM / llama.cpp | http://your-host:port/v1 | your model name |
The widget calls the LLM directly from the browser, so the provider must allow CORS from your share-page origin. Most do; if yours doesn't, run a small CORS proxy in front.
How it works
- Index build โ on first message, the widget walks your shared note tree using Trilium's public
/share/api/notes/:idendpoint and builds an in-memory index:{ noteId, title, breadcrumb, content }for every text note. - TOC injection โ the system prompt sent to the LLM includes a compact Table of Contents (one line per note:
- Title [noteId] (breadcrumb)). - Tool loop โ the AI sees the TOC, calls
search_notes(query)to find candidates, thenread_note(noteId)to fetch full bodies. Tools execute locally against the cached index โ no extra network round-trips per call. - Answer โ when the LLM stops calling tools, the final text answer renders with light Markdown (paragraphs, lists, bold,
code, links).
This keeps conversation context small (just the TOC + selectively-fetched note bodies), so token bills stay low even on a 500-note knowledge base.
Architecture
- Single file, ~740 lines, plain ES2020+
- No build step, no bundler, no dependencies
- Renders inside Shadow DOM attached to a single
<div>host on<body>โ full style isolation in both directions - Inline Feather SVG icons built via
createElementNS(no external icon CDN) - All settings + history โ
localStorageunder thetrilium-ai:*namespace - CSS custom properties drive colors โ tracks Trilium's theme variables (
--background-primary,--background-highlight,--text-primary) so the widget respects light/dark themes
Security & privacy
- API keys never leave the visitor's browser. Stored in
localStorage, sent directly to the configured base URL viafetch(). - No telemetry. The widget makes exactly the requests you'd expect: Trilium's
/share/api/notes/*to fetch your docs, and the LLM provider's/chat/completions. - Each visitor pays for their own usage โ no shared API budget, no server-side key.
- The widget calls only the base URL configured in settings. No silent fallback.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Bubble doesn't appear | ~shareJs relation missing or not inheritable. Network tab โ api/notes/<your-js-id>/download should return 200 with JS. |
meta REPLACE_ME 404 in console | You forgot to set ROOT_NOTE_ID in the script. |
LLM 401: invalid_api_key | Wrong key. The Test button in settings will tell you which line of the response failed. |
LLM 400: The reasoning_content... (DeepSeek) | The widget already handles this โ preserves reasoning_content across turns. Hard-refresh if you still see it. |
| Tool calls not visible in chat | "Show tool calls in chat" toggle is off โ flip it in Settings. |
| Settings panel won't scroll / Save button hidden | Hard-refresh; you might be on a stale cached copy. |
| CORS error in console | Provider doesn't allow browser-origin requests. Pick a different provider or set up a CORS proxy. |
Turn on Debug mode in Settings to see every LLM request/response and tool call in the browser console โ usually pinpoints the issue in 30 seconds.
Roadmap
- Streaming responses (token-by-token rendering)
- Light-theme-aware syntax highlighting palette in
code-blocks.css - Conversation export (Markdown / JSON)
- Per-page suppress: hide widget on specific notes via
#noAilabel - Cite-with-link (AI answer adds a clickable note link automatically)
Contributing
PRs welcome. Keep the no build step invariant โ this should always be a single widget.js you can paste into a Trilium note.