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) and read_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.

FieldWhat it does
Base URLYour provider's OpenAI-compatible endpoint
API keyBearer token, sent only to that base URL
ModelModel name string for that provider
System promptFully editable. Reset to default restores it.
Show tool calls in chatToggle the โ€บ Searching: "..." / โ€บ Reading: ... breadcrumbs
Debug modeLog every LLM request/response/tool call to console
Test buttonPings /chat/completions with "ping" to verify base + key + model

Provider examples

ProviderBase URLSample model
Google Geminihttps://generativelanguage.googleapis.com/v1beta/openaigemini-2.5-flash
OpenAIhttps://api.openai.com/v1gpt-4o-mini
OpenRouterhttps://openrouter.ai/api/v1anthropic/claude-sonnet-4-5, google/gemini-2.5-pro, โ€ฆ
DeepSeekhttps://api.deepseek.comdeepseek-chat, deepseek-reasoner
Groqhttps://api.groq.com/openai/v1llama-3.3-70b-versatile
Local Ollamahttp://localhost:11434/v1whatever you've pulled
Self-hosted vLLM / llama.cpphttp://your-host:port/v1your 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

  1. Index build โ€” on first message, the widget walks your shared note tree using Trilium's public /share/api/notes/:id endpoint and builds an in-memory index: { noteId, title, breadcrumb, content } for every text note.
  2. TOC injection โ€” the system prompt sent to the LLM includes a compact Table of Contents (one line per note: - Title [noteId] (breadcrumb)).
  3. Tool loop โ€” the AI sees the TOC, calls search_notes(query) to find candidates, then read_note(noteId) to fetch full bodies. Tools execute locally against the cached index โ€” no extra network round-trips per call.
  4. 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 โ†’ localStorage under the trilium-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 via fetch().
  • 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

SymptomLikely 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 consoleYou forgot to set ROOT_NOTE_ID in the script.
LLM 401: invalid_api_keyWrong 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 hiddenHard-refresh; you might be on a stale cached copy.
CORS error in consoleProvider 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 #noAi label
  • 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.