Skip to Content
DocumentationUI/UXPrebuilt Chat UI

Prebuilt Chat UI

Cascaide templates ship with a prebuilt Chat UI built with React. It is easily hackable.

It’s more than just a UI, it’s a participant in the graph. It handles initiating cascades and observing it, We also leverage it to initiate cascades for sub agent delegations. This pattern of using the client to handle sub agent delegations is especially useful in serverless environments to preserve graceful timeout handling.

What we hope to convey here are patterns. Treat this as an example implementation to draw inspiration from or to modify.

Check out the code at https://github.com/cascaide-ts/cascaide-nextjs-starter/tree/main/components/cascaide-ui/chat 

Installation Guide

You can install it into your project using

npx shadcn@latest add https://raw.githubusercontent.com/cascaide-ts/cascaide-ui/main/registry/chat.json

Overview

This is a prebuilt chat interface built on top of cascaide-ts, a multi-agent orchestration framework. It supports three agent types out of the box — a hotel booking supervisor, a ReAct search agent, and a recursive deep-search agent — and includes full support for human-in-the-loop (HITL) tool UIs, sub-cascade delegation, and streaming conversation state.


File Structure

├── chat.tsx # Root component — layout, state, message dispatch ├── input.tsx # InputBar — textarea, send button, agent cards ├── message-bubble.tsx # Renders individual messages (text, reasoning, tool calls) ├── message-list.tsx # Conversation scroll view + delegation orchestration ├── sidebar.tsx # Collapsible navigation sidebar with chat history └── tool-ui/ └── hotel-tool.tsx # Example HITL tool: hotel selection UI

Components

Chat (chat.tsx)

The root component. Manages global state and wires together all child components.

Props

PropTypeDescription
nodeIdstringThe workflow node ID used to register active nodes via useWorkflow

Key responsibilities

  • Maintains the active chatId (a UUID, reset on agent change or new chat)
  • Subscribes to cascade state via useCascade(chatId)
  • Derives conversationMessages from cascadeState.history, with an optimistic pending user message spliced in while the cascade catches up
  • Dispatches messages by building a Spawns object and calling addActiveNode
  • Handles tool responses from HITL tools via handleToolResponse

Agent types

type AgentType = | 'hotelSupervisorNode' | 'searchAgentNode' | 'recursiveSearchAgentNode';

Switching agents resets chatId and clears conversation state. The selected agent determines which key is used in the Spawns object when sending a message.


InputBar (input.tsx)

The text input area shown both on the empty-state landing screen and in the active conversation footer.

Props

PropTypeDescription
inputstringControlled textarea value
isProcessingbooleanDisables input while a cascade is running
userIdstringDisables input if falsy
compactbooleanReduces padding and hides agent cards (used in the footer)
onChange(value: string) => voidUpdates input state
onSend() => voidTriggered on button click
onKeyPress(e: KeyboardEvent) => voidTriggered on Enter (Shift+Enter inserts a newline)

Note: The InputBar in input.tsx declares selectedAgent and onAgentChange props but these are not yet wired up — agent switching is currently handled in chat.tsx directly. The AGENTS array and the older PROMPT_SUGGESTIONS array both exist in the file; PROMPT_SUGGESTIONS is what’s actually rendered in the suggestion cards.

Agent suggestion cards

In non-compact mode, three clickable prompt cards are shown below the input. Clicking one populates the textarea and immediately sends the message. These map to the three agent types and auto-fill representative starter prompts.


MessageList (message-list.tsx)

Renders the full conversation history and orchestrates sub-cascade delegation.

Props

PropTypeDescription
displayHistoryCanonicalMessage[]Full conversation history to render
userIdstring | undefinedPassed through to MessageBubble
addActiveNodefunctionUsed to spawn sub-cascades for delegation
handleToolResponsefunctionCalled when a HITL tool or sub-cascade completes

Tool registry

export const toolRegistry: Record<string, React.ComponentType<ToolComponentProps>> = { present_hotel_options: HotelOptions, };

Register your own HITL tool components here. The key must match the tool name the LLM uses in its tool call.

Delegation flow

When the supervisor agent calls a tool named delegate_to_<agentName>, MessageList automatically:

  1. Detects the delegation call in the latest assistant message
  2. Spawns a new sub-cascade with a fresh UUID (sub_cascade_<uuid>)
  3. Mounts a CascadeMonitor that subscribes to the sub-cascade
  4. When the sub-cascade completes, calls handleToolResponse with the result, which sends it back to the supervisor as a tool result message

Allowed delegation targets:

const ALLOWED_AGENTS = ['availabilityAgentNode', 'bookingAgentNode'];

MessageBubble (message-bubble.tsx)

Renders a single message. Uses an internal block registry to dispatch different content types.

Content blocks (in order)

BlockRenders when
thinkingmessage.thinking is present — collapsible reasoning panel
contentmessage.content is present — Markdown-rendered text
tool_callsmessage.tool_calls is present — tool call cards

User messages render as a right-aligned gray pill with a “U” avatar. Tool result messages (role === 'tool') are not rendered directly — they are looked up and associated with the corresponding tool call.

Tool call rendering

Each tool call is rendered by ToolCallRenderer. If the tool name exists in toolRegistry, the registered component is used. Otherwise, a generic collapsible card shows the tool name, a running/executed badge, and the raw JSON args.


A collapsible navigation panel.

Props

PropTypeDescription
historyany[]Array of past chat objects with id, title, messages, lastUpdated
currentChatIdstringHighlights the active chat
onNewChat() => voidStarts a fresh conversation
onSelectChat(id: string) => voidSwitches to an existing conversation
isOpenbooleanControls mobile visibility
onClose() => voidCloses on mobile
isExpandedbooleanWide (label visible) vs. narrow (icon-only) on desktop
toggleExpansion() => voidToggles expanded state

Note: onSelectChat is a stub — loading historical conversation state is marked TODO in chat.tsx.


HotelOptions (tool-ui/hotel-tool.tsx)

The canonical example of a HITL (Human-in-the-Loop) input tool.

How it works

  1. The LLM calls the present_hotel_options tool with a list of hotels
  2. MessageList routes this to HotelOptions via toolRegistry
  3. The user selects a hotel and room type
  4. onComplete is called with a result string: "selected [Hotel Name] and [Room Type]"
  5. The cascade resumes with this as the tool result

Props (via ToolComponentProps)

PropTypeDescription
argsRecord<string, any>LLM-provided tool arguments; expects args.hotels[]
onComplete(result: string) => voidCall when user makes a selection
isFinishedbooleanTrue after a selection has been made — makes the UI read-only
savedResultstring | nullRe-parsed to highlight the previously selected option on reload

Building your own tool

  1. Create a component matching ToolComponentProps
  2. Call onComplete(yourResultString) when the user acts
  3. Use isFinished to make the UI read-only
  4. Register it in toolRegistry in message-list.tsx

ChartRenderer (chart-renderer.tsx)

A standalone chart display component built on Recharts. Not currently wired into the agent tool registry, but ready to register as a tool UI.

Supported chart types: line, bar, area, pie

Props (ChartDisplayProps)

PropTypeDescription
chartTypestringOne of line, bar, area, pie
titlestringCard title
dataany[]Recharts-compatible data array
xAxisKeystringKey used for the X-axis / pie name
dataKeysstring[]Keys for each series or the pie value
colorsstring[]Hex color array, cycled across series
userIdstringUser identifier
idstringRecord ID (for future persistence)

Data Flow

User types → handleSendMessage() → setPendingUserMessage() (optimistic UI) → addActiveNode(spawns) (dispatches to cascaide-ts) → useCascade(chatId) updates (streaming state) → conversationMessages derived from cascadeState.history → MessageList renders messages → tool call detected → HITL tool rendered → user acts → onComplete() called → handleToolResponse() → addActiveNode() resumes cascade

For delegated sub-agents:

LLM calls delegate_to_<agent> → MessageList detects delegation call → new sub-cascade spawned (sub_cascade_<uuid>) → CascadeMonitor mounts, subscribes via useCascade(subCascadeId) → sub-cascade completes → handleDelegationComplete() called → supervisor receives tool result → cascade resumes

Dependencies

PackagePurpose
@cascaide-ts/reactuseWorkflow, useCascade hooks
@cascaide-ts/coreSpawns type
@cascaide-ts/helpersCanonicalMessage type
react-markdownMarkdown rendering in messages
remark-gfmGitHub Flavored Markdown
remark-math + rehype-katexLaTeX math rendering
rehype-highlightCode block syntax highlighting
rechartsCharts in ChartRenderer
lucide-reactIcons
uuidChat and sub-cascade ID generation
Last updated on