Chatbot Build

How was the docs chatbot implemented?

Docs Indexing

  • public: false pages are still indexed — /docs/chat applies FLAG_PUBLIC at query time so a public build only retrieves public: true chunks.
  • Inline user guide search and /llms.txt keep the existing flags.public filter and are separate from the chatbot index.

src/lib/docs/compose.ts with forIndex: true builds the text that later goes into docs_section:

  • Reads the docs MDX files from a Git branch (BRANCH, default main).
  • Shared _snippets files are pasted into the page that imports them, so the index stores the full wording, not an import line.
  • Each chunk is marked public: true or false from the page frontmatter, so chat can filter later.
  • A page becomes one overview chunk plus one chunk per heading (the heading's href may include #anchor).

Chatbot Interface

Clicking the ask the documentation button (DocsChat) opens the DocsChatPanel. /docs/chat/route.ts:

  • Restores message history it the browser has a docs_chat_id cookie. (GET)
  • Looks up matching docs in docs_section, then asks Gemini to write a summary from those excerpts only on Send. (POST)
  • Clears the message history by dropping the current docs_chat_id cookie and the matching Supabase session when Clear Chat is clicked. (DELETE)
  • Gemini (gemini-embedding-001, 768 dimensions, RETRIEVAL_QUERY) turns the question into a vector so pages can match by meaning, not only by the same words. If embed quota is hit, keyword search still runs.
  • The same wording reuses docs_query_embedding (scripts/supabot.sql). The row ID is a hash of the question, so the question text is not stored. The server instance also keeps vectors it has already loaded, and a trigger keeps the newest 200 rows. Row-level security is on with no anon / authenticated policies. /docs/chat reads and writes those rows with the service_role client.
  • Postgres then runs two lookups — wording match (search_docs_sections, ranked on search_vector) and meaning match (match_docs_sections, cosine distance). Those ranked lists are combined (RRF) in /docs/chat so a page that does well on either can still surface.
  • At most three pages are kept, and only if their combined score is close to the best hit. Weaker matches are dropped.
  • Privacy Policy, Terms of Service, and Cucumber Tests are pushed down unless the question is actually about them.
  • Short words such as api and add are kept for matching (three characters or more).
  • If a heading on that page matches the question, that heading's excerpt is used instead of the page overview.
  • When FLAG_PUBLIC is on, only public: true pages are retrieved. That filter runs inside each lookup, before the row limit.

Chat History

  • docs_chat_session, docs_chat_message, and docs_chat_limits tables in Supabase created by scripts/chat-history.sql store chat history per browser session and chat IP quotas.
  • The browser never talks to these tables — row-level security is on with no anon / authenticated policies. /docs/chat reads and writes session and message rows with the service_role client — quotas go through DATABASE_URL.
  • docs_chat_session is one conversation — cookie docs_chat_id (httpOnly, path /) stores that row's ID so opening ask the documentation again restores the same thread within that browser session.
  • Send upserts that session (a leftover cookie still captures) and writes the question immediately, then the reply after Gemini finishes.
  • Clear Chat deletes that cookie and this session row.

Chatbot Reference

Loading API reference…