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.
| Component | File | Responsibility |
|---|---|---|
ChatWindow | components/Chat/ChatWindow.tsx | Message list, VoiceOrb, confirmation banner, plan progress. |
MessageBubble | components/Chat/MessageBubble.tsx | Single message render — markdown, code blocks, widgets, copy. |
InputBar | components/Chat/InputBar.tsx | Auto-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.
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:
| Role | Alignment | Styling |
|---|---|---|
assistant | Left | Dark surface card, markdown rendered, code blocks with syntax highlight. |
user | Right | Accent 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.
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.
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.
// Set by the SSE handler when type === "confirm_request"
pendingConfirmation: {
confirm_id: string
message: string
} | nullPlanProgressBar
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.
activePlan: {
current: number
total: number
label: string
} | nulluseChat 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.
const {
sendMessage, // (text: string) => void
loadConversation, // (id: number) => Promise<void>
newConversation, // () => void
isLoading, // boolean — true while streaming
} = useChat()sendMessage flow
- Appends user message to the store and calls
POST /api/chat/stream. - Opens an SSE stream and processes each event type (see below).
- Appends tokens to the current assistant message in real-time.
- 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:
| Type | Payload fields | Effect |
|---|---|---|
token | content | Appended to the current streaming message. |
tool_call | tool, args | Displayed as a collapsible tool-call block. |
tool_result | tool, result | Appended under the matching tool-call block. |
widget | kind, data | Renders a dynamic widget inline in the message. |
confirm_request | confirm_id, message | Sets pendingConfirmation → shows the banner. |
plan | current, total, label | Sets activePlan → shows the progress bar. |
command | type, … | Executes a bracket command (browse, launch, Spotify, etc.). |
done | — | Closes the stream, marks message complete. |
error | message | Displays an error message in the chat. |