Chat panel
The Ask drawer for grounded conversation with the garden. One panel, two bots (a per-page chat bot and a RAG ask bot), switched via a mode toggle. Plus contextual follow-up chips and a system-prompt picker.
A floating “Ask” button in the bottom-right opens a slide-in drawer. The drawer hosts two bots behind a mode toggle: “This page” (the per-page chat assistant, knows the page in front of you and the garden index) and “The garden” (a RAG bot that retrieves relevant posts and quotes from them). Each mode keeps its own thread; switching does not blend them. Both modes persist across navigation.
Layout
- Toggle: pill-shaped button bottom-right, accent-2 colored, casts a soft shadow. Hides itself when the drawer is open.
- Drawer: fixed right edge, default 420px, resizable from a draggable left edge (pointer + arrow keys), maximizable via header button.
- Header: title, context pill (“Talking about X”), mode toggle (“This page” / “The garden”), settings cogwheel + reset / maximize / close icons.
- History: scrollable region with assistant + user bubbles, each prefixed by an avatar. Assistant avatar is the watercolor leaf at 32px (circular crop); user avatar is the watercolor acorn at 34px (no crop, full painted shape, transparent background).
- User bubble: sand-colored background (
--chat-tag-bg: #ECE2CElight,#352E25dark), scoped to the drawer so the wider site’s tag-bg can stay distinct. - Input: depends on mode. In chat mode there is no separate textarea: the fourth pebble in the chips area IS the input (type, press Enter). In ask mode the chips are hidden and a regular textarea form surfaces at the bottom of the drawer. The form element is the same in both cases; CSS toggles its visibility via
data-modeon the drawer. - A small disclaimer line below the chips: “The Garden can be wrong. Check the page.”
Mode toggle: This page / The garden
A small segmented control in the panel header switches between two backends:
- This page (
/api/chat) is the per-page chat bot. Receives the current page’s body and the flat garden index as context. Knows everything about the page in front of you, can name-drop other posts from the index but cannot quote them. - The garden (
/api/ask) is the RAG bot. Runs retrieval against the whole garden using the taxonomy, themes, and triples, then sends the matched post bodies to the model. Quotes from the posts and emits inline[1]/[s1]citation markers. Same backend as the standalone/research/askpage.
Each mode keeps its own thread. Switching modes does not clear or merge them; both are kept under the same garden-chat-v2 storage key in sessionStorage ({mode, chat: {messages}, ask: {messages}, open}). The active mode also persists in localStorage under garden-chat-mode so it survives page reloads.
Behavioural differences:
- Chat mode: pebble chips render after every reply (model-generated when the v0.2 prompt is active, falling back to a static pool). The settings cogwheel reveals the prompt picker. The textarea form is hidden.
- Ask mode: no pebble chips, no cogwheel. A collapsible “N sources · M topics” block renders above each assistant reply, listing the matched articles and topics with their
[cid]numbers. Citation markers in the answer text are rewritten live as the stream arrives:[s2]becomes a pink inline link to the matched article,[1]becomes a numbered chip linking to the topic page. The textarea form is visible.
The toggle aborts any in-flight request before switching, so a slow ask reply doesn’t bleed into the chat thread.
Two parallel chat designs (legacy note)
Two designs are on the table for the chatbot, kept separate so Maaike can compare and pick one:
- Classic (live, on every page) — what visitors see today: vertical chat with avatars + bubbles + the four scattered pebble chips (three suggested + one input). Files:
src/components/ChatPanel.astro,src/scripts/chat-panel.ts. - Mycelium (standalone prototype at
/chat-mycelium-prototype.html) — a wide-view experiment where the right half of the drawer renders an SVG mycelium that grows as the conversation does, plus a curated summary (threads pulled, posts cited, open questions raised) and a “Submit this conversation to the garden” action. Self-contained HTML; no backend wiring. Click through with stepper buttons to evaluate.
The renderMycelium function and SVG_NS constant remain in chat-panel.ts (not called) for ease of re-enabling when a choice is made. The mycelium markup, CSS, and ResizeObserver wiring have been removed from ChatPanel.astro/init flow so the live chatbot is unchanged.
Cedarville Cursive (chat-only spidery accent)
Several short, secondary, decorative texts in the chat use Cedarville Cursive (self-hosted at public/fonts/cedarville-400-latin.woff2) for a handwritten “margin note” feel. Specifically:
- The follow-up chip text
- The header context pill (“Talking about Page Title”)
- The role labels above each bubble (“You” / “The Garden”)
- The settings popover header (“System prompt”)
- The input field’s placeholder (“Ask about this page, or anything in the garden…”)
What stays in the body font (Nunito / current picker choice): bubble text, send button, settings dropdown options, the small disclaimer line, the drawer title.
Rule of thumb: Cedarville for short, annotational, decorative text. Body sans for anything the user reads as content or interacts with directly.
Mobile rules
- ≤800px: drawer goes full-width (
width: 100vw), the resize handle is hidden. - Tapping any internal link inside the chat closes the drawer so the destination page is visible. External links (
target="_blank") leave the drawer open. - The input is never auto-focused on mobile. Keyboard only appears when the user explicitly taps the textarea. On desktop, focus behavior is unchanged (focus on open / reset / after streaming finishes).
Follow-up chips
After every assistant turn, three follow-up chips render below the bubble. They invite the user to keep tapping instead of typing. The empty state (chat just opened, no messages yet) uses the same chip layout for its starter suggestions.
Design intent: scattered watercolor stones, like pebbles tossed on a beach. Each chip is a small painted pebble (~100×36) with a soft drop-shadow and its caption written below in Cedarville. The chip set always renders four pebbles: three follow-up suggestions plus a fourth “input pebble” the visitor can type into directly.
- Pebble PNGs:
/images/watercolor-pebble-1/2/3.png, used as background-image on a small.chip-pebbleelement above the caption (variant F: pebble as a layer, not a stretched fill). - Caption font: Cedarville Cursive, the chat-only spidery accent.
- Input pebble (4th chip): same pebble shape, contains an
<input>instead of a static caption. Pressing Enter submits the typed text exactly as if a chip had been clicked.
Desktop: heap layout
On viewports wider than 800px the chips area is a 150px-tall position: relative box, and each chip is position: absolute at its own left / top plus a tilt. The four pebbles overlap into a small pile. Captions are hidden until interaction. Hovering, focusing, or focus-within on a chip lifts it (translateY(-8px) rotate(0deg) scale(1.04)), bumps z-index, and fades the caption in. The input pebble’s <input> field becomes interactive on hover/focus (opacity: 0 → 1, pointer-events: none → auto).
Mobile: flat row
At ≤800px the heap CSS is overridden: chips return to position: relative, the followups container becomes display: flex; flex-wrap: wrap, and captions stay always visible (no hover on touch). Per-chip translate offsets give a casually-scattered look without the overlap.
No SVG filter
Earlier versions used the global #sketchy feTurbulence displacement filter on chip borders. The watercolor pebble PNGs already carry the hand-painted wobble, so the filter is no longer applied.
Source: contextual, model-generated (v0.2 prompt)
The chip-driven flow is scoped to the v0.2 garden system prompt. v0.1 still has the original in-prose handoff design. The settings cogwheel lets visitors flip between them. When v0.2 is active, the model is instructed to append a single line at the very end of every reply:
<<CHIPS:["question one","question two","wander question"]>>
The backend stream parser watches for this marker, suppresses everything from <<CHIPS: onward in the visible token stream, and once the closing >> arrives, parses the JSON and emits a separate {type:"chips",items:[...]} event. The marker text never reaches the user’s screen.
The model is told to make item 1 and item 2 close-context (rooted in the just-given answer + page) and item 3 a “wander” chip — an unexpected connection elsewhere in the garden. Chips replace on every turn.
If the marker is missing or malformed, the frontend falls back to pickSerendipityChips(3), which draws from the static SERENDIPITY_PROMPTS pool in src/scripts/chat-panel.ts (examples: “Find a strange neighbour”, “Surprise me”, “Where does this break?”). The static pool is the safety net, not the primary path.
System-prompt picker (settings cogwheel)
The header has a small cogwheel icon. Clicking it opens a popover anchored to the gear with a <select> listing the active and draft prompts wired to bot_id: garden (read from src/content/prompts/). Switching prompt mid-conversation prompts a confirm dialog and clears the conversation on accept.
The popover is hidden by default and reveals only on click. The gear itself stays hidden until /api/prompts returns more than one option, so single-prompt deployments don’t show the affordance at all.
Behavior
- Chips render after a successful assistant message and after restoring a conversation from sessionStorage where the last message is from the assistant.
clearFollowups()runs at the start of every send so stale chips never linger past the new question.- Clicking a chip fills the input with the chip text and submits the form. The submit path is identical to typing the text manually.
Persistence
Conversation lives in sessionStorage under garden-chat-v2 so it survives Astro ViewTransitions. Shape: {mode: 'chat'|'ask', chat: {messages: [...]}, ask: {messages: [...]}, open: bool}. Ask-mode assistant messages also store their sources[] so the collapsible sources block + inline citations can be re-rendered after a navigation. Pane width and maximize state live in localStorage under garden-chat-pane-v1. Active mode is mirrored to localStorage under garden-chat-mode.
Tracing
Every send opens a Langfuse trace, tagged bot:chat or bot:ask, with a hashed user_id and a per-page-load session_id minted by the frontend. Ask-mode traces include a retrieval span (matched articles, topics, fired triples). Both tag the generation with the prompt version pulled from Langfuse, so prompt-version analytics work for both bots. See the dedicated Langfuse integration entry for trace shape and prompt source-of-truth rules.
Files
src/components/ChatPanel.astro, markup + all CSS (global)src/scripts/chat-panel.ts, init, rendering, streaming, chip logicsrc/components/SketchyFilter.astro, the#sketchySVG filter, included byPageLayout