Reading transcripts
What every element in an open conversation shows — header, summary banner, message bubbles, source citations, timestamps.
Once you pick a conversation from the list (see Conversations overview), the right panel renders the MessagePanel component. This page describes every element in that panel.
Header
The header is a three-row info block on the left plus status action buttons on the right.
Left block:
-
A circular avatar showing
visitor_name[0](orsession_id[0], or?) uppercased -
Visitor name —
visitor_nameif set, otherwise the first 16 chars ofsession_id -
Visitor email — only rendered if set. Clickable
mailto:link. -
Visitor phone — only rendered if both email AND phone are set, prefixed by
·. Clickabletel:link. -
A row of metadata: a channel dot (color per channel — green WhatsApp, blue Telegram, muted widget), the literal
{N} messages, and a status badge.
Status badges:
-
Needs reply (amber dot + pill) when
status === "handed_off" -
Closed (muted pill) when
status === "closed" - No badge for active conversations
Right block — status action buttons (see Replying as an agent for what they do).
On mobile (lg:hidden), a left-pointing chevron sits in the leftmost position of the header to take you back to the conversation list.
Handoff summary banner
A banner only appears when both conditions hold: status === "handed_off" AND conversation.handoff_summary is truthy.
┌──────────────────────────────────────────────────────────┐
│ Summary: │
└──────────────────────────────────────────────────────────┘
Rendered inside the panel, below the header, with amber border/background. Format: a bold Summary: prefix, then the summary text.
How the summary is generated
Verified from Chatbotgen.Workers.HandoffNotification:
-
Runs in the Oban
:defaultqueue after a handoff. -
Uses the LLM via
LLMClientwith modelgoogle/gemini-2.5-flash,max_tokens: 200,temperature: 0.2. -
Transcript fed to the model is the last 20 messages joined as
{role}: {content}lines. -
Prompt (verbatim):
Summarize this customer support conversation in 2-3 concise sentences. Focus on: (1) what the user is asking for, (2) what the AI has already tried, (3) why a human agent is needed. Do not add pleasantries — just the summary.
-
If the model returns a non-empty string, it's saved onto the conversation's metadata under
"handoff_summary"and broadcast viaChatbotBroadcast.handoff_summary/3. If generation fails, a warning is logged and nothing is persisted.
Until the worker finishes, the banner is absent. After it finishes, the banner renders on the next Inertia reload or channel update.
Message thread
Each message renders as a MessageBubble. Layout depends on the role:
-
user— right-aligned, primary-color background,rounded-br-md -
agent— left-aligned, blue-tinted background (bg-blue-100/ dark equivalent),rounded-bl-md -
everything else (typically
assistant) — left-aligned, muted background,rounded-bl-md
Agent bubbles prepend the agent's name above the content in smaller semibold text.
Each bubble contains:
-
Agent name (agent messages only, when
msg.agent_nameis set) -
msg.content— rendered withwhitespace-pre-wrap(line breaks and leading spaces preserved) -
Sources footer — appears only when
msg.sourcesis a non-empty array. Format:Sources: {joined with ", "}. -
Timestamp in the bottom-right corner —
HH:MMper the user's locale.
Reply box
Only rendered when conversation.status === "handed_off". Documented in Replying as an agent.
Auto-scroll
On every change to conversation.id or messages, the thread's scroll container is pinned to the bottom (scrollTop = scrollHeight). There's no manual scroll-to-bottom button — new messages just appear at the bottom.
What the header does NOT show
- No agent assignment or team handoff — any logged-in member can reply
- No "first responded at" metric
- No chatbot indicator (you're already inside a chatbot's conversations tab)
Related
- Conversations overview
- Replying as an agent
-
Human handoff — what flips a conversation to
handed_off