Chat UI

ChatWindow, MessageBubble, and InputBar — the core chat interface components and the useChat hook that drives them.

Overview

The chat interface is built from three composable React components that share state through Zustand. All chat logic lives in the useChat hook — the UI components are purely presentational.

ComponentFileResponsibility
ChatWindowcomponents/Chat/ChatWindow.tsxMessage list, VoiceOrb, confirmation banner, plan progress.
MessageBubblecomponents/Chat/MessageBubble.tsxSingle message render — markdown, code blocks, widgets, copy.
InputBarcomponents/Chat/InputBar.tsxAuto-growing textarea, send button, keyboard shortcuts.

ChatWindow

The root chat component. Renders the message scroll area, the inlineVoiceOrb (when voice is active), an animatedConfirmationBanner for tool approval prompts, and aPlanProgressBar for multi-step coding agent tasks.

usage
import { ChatWindow } from './components/Chat/ChatWindow'

// Renders the full chat panel including InputBar and VoiceOrb
<ChatWindow />

Layout

  • Top bar — conversation title, copy-all button, new-chat button.
  • Scroll area — auto-scrolls to bottom on new messages. Animated entry for each bubble.
  • Voice orb — appears below messages when voice is enabled.
  • InputBar — fixed to the bottom.

Confirmation banner

When the coding agent requests confirmation for a destructive tool call (code_run_shell, file overwrites, etc.), an amber banner slides in from the top with Yes / No buttons. Clicking callsPOST /api/confirm/{confirm_id} and clears the banner.

MessageBubble

Renders a single chat message. Handles Luna and user roles differently:

RoleAlignmentStyling
assistantLeftDark surface card, markdown rendered, code blocks with syntax highlight.
userRightAccent bubble, plain text.

Luna messages support:

  • Full GitHub-flavored markdown (headings, lists, blockquotes, tables).
  • Fenced code blocks with copy button and language label.
  • Dynamic widget rendering — charts, comparison cards, timelines injected via SSE.
  • Tool call / tool result display (collapsible).
  • Inline streaming — characters appear as SSE tokens arrive.
props
interface MessageBubbleProps {
  role: "user" | "assistant"
  content: string
  widgets?: Widget[]
  isStreaming?: boolean
}

InputBar

Auto-growing textarea capped at 160 px height. Submits onEnter (Shift+Enter for a newline). Disabled while a response is streaming.

props
interface InputBarProps {
  onSend: (message: string) => void
  disabled?: boolean
}

Displays a character count overlay when the input is non-empty.

ConfirmationBanner

Reads pendingConfirmation from the Zustand store. When a confirmation request arrives (via SSE confirm_request event), the banner slides in with the message and Yes / No buttons.

store shape
// Set by the SSE handler when type === "confirm_request"
pendingConfirmation: {
  confirm_id: string
  message: string
} | null

PlanProgressBar

A thin progress bar that appears at the top of the chat when the coding agent sends a multi-step execution plan. Reads activePlanfrom the store.

store shape
activePlan: {
  current: number
  total: number
  label: string
} | null

useChat hook

All chat logic — sending messages, opening SSE streams, handling events, loading conversation history — lives in hooks/useChat.ts. Components call its methods; they never talk to the API directly.

exported interface
const {
  sendMessage,          // (text: string) => void
  loadConversation,     // (id: number) => Promise<void>
  newConversation,      // () => void
  isLoading,            // boolean — true while streaming
} = useChat()

sendMessage flow

  1. Appends user message to the store and calls POST /api/chat/stream.
  2. Opens an SSE stream and processes each event type (see below).
  3. Appends tokens to the current assistant message in real-time.
  4. On done, marks streaming complete.

SSE event types

The chat stream endpoint sends Server-Sent Events. Each event is a JSON object with a type field:

TypePayload fieldsEffect
tokencontentAppended to the current streaming message.
tool_calltool, argsDisplayed as a collapsible tool-call block.
tool_resulttool, resultAppended under the matching tool-call block.
widgetkind, dataRenders a dynamic widget inline in the message.
confirm_requestconfirm_id, messageSets pendingConfirmation → shows the banner.
plancurrent, total, labelSets activePlan → shows the progress bar.
commandtype, …Executes a bracket command (browse, launch, Spotify, etc.).
doneCloses the stream, marks message complete.
errormessageDisplays an error message in the chat.