Cognition

Components

Conversational UI

The canonical AI conversation surface for Distyl products. Composed from ChatShell, MessageBubble, ChatInput, and LoadingBubble — each independently usable. Not a modal or a drawer. A panel or inline surface that supports a full turn-based AI exchange.

Distyl-specific. This component has no external library counterpart or design-tool source yet. It is the canonical pattern for AI chat surfaces across Distyl products — Platform, Tower, and future implementations.

Preview

This preview is live — it's running Meno, Cognition's built-in assistant. Ask it anything about tokens, components, or system rules.

Context view

Ask Meno anything about Cognition

Tokens, components, and the rules that hold the system together.

Enter to send · Shift+Enter for newline

Variants

Context view

Ask Meno anything about Cognition

Tokens, components, and the rules that hold the system together.

Enter to send · Shift+Enter for newline

No props. Uses /api/chat with the default system prompt.

States

Empty

Ask Meno anything about Cognition

Tokens, components, and the rules that hold the system together.

Loading

You

What are the risk signals in this dataset?

Meno

Thinking…

Error

You

Summarize this pipeline.

Meno

Error: API error: 401

The error state is a danger-styled MessageBubble (the error prop) — alert icon, danger border, and danger surface. The message string is set by the catch block in ChatShell.

API

Prop
Type
Default
Description
systemPrompt
string
undefined
System prompt forwarded to the API route. Each product context should override this.
placeholder
string
"Ask the context view…"
Input placeholder text.
onSend
(messages, userMessage) => Promise<string>
undefined
Optional escape hatch. If provided, ChatShell calls this instead of /api/chat. Use for products with their own API layer.
ChatShellComposes all sub-components. Owns message state, API call, and auto-scroll.
MessageBubbleRenders a single user or assistant turn. Parses markdown and fenced code blocks.
ChatInputAuto-resizing Textarea with send Button. Handles Enter-to-send and Shift+Enter newline.
LoadingBubbleAssistant thinking state. Uses the Distyl Spinner.
EmptyStateZero-message state with prompt chips.

Sub-components

ChatInput

Auto-resizing message input with keyboard shortcuts. Composes Textarea and Button.

Enter to send · Shift+Enter for newline

Prop
Type
Default
Description
onSend
(message: string) => void
required
Called with the trimmed message string on Enter or button click.
disabled
boolean
false
Disables the Textarea and send Button. Set while a response is loading.
placeholder
string
"Ask the context view…"
Input placeholder text.
import { ChatInput } from "@/components/ConversationalUI";

export function MyInput() {
  return (
    <ChatInput
      onSend={(message) => console.log(message)}
      placeholder="Ask something…"
    />
  );
}

MessageBubble

Renders a single conversation turn. User turns are plain text. Assistant turns parse markdown and fenced code blocks.

You

What are the risk signals in this dataset?

Meno

There are three primary risk signals:

First, the counterparty exposure in the derivatives book exceeds the 15% threshold set in the risk policy.

Second, two entities flagged as high-risk are connected to the primary party via undisclosed intermediaries.

Meno

Use the riskScore field on the Entity type to filter above threshold.

Meno

Here's a query to pull flagged entities:

const flagged = entities.filter(
  (e) => e.riskScore > 0.7
);

Meno

Error: API error: 401
Prop
Type
Default
Description
role
"user" | "assistant"
required
Determines alignment, background, and content rendering.
content
string
required
Message text. Assistant role parses inline code and fenced code blocks.
error
boolean
false
Renders an assistant turn in the danger style with an alert icon.
import { MessageBubble } from "@/components/ConversationalUI";

// User turn
<MessageBubble role="user" content="What are the risk signals?" />

// Assistant turn — parses markdown and code blocks automatically
<MessageBubble
  role="assistant"
  content="Here's the query:\n\n```typescript\nentities.filter(e => e.riskScore > 0.7)\n```"
/>

LoadingBubble

Assistant thinking state. Uses the Distyl Spinner. No props — render it while awaiting a response.

Meno

Thinking…

No props. Render LoadingBubble while loading === true and remove it when the reply arrives.

import { LoadingBubble } from "@/components/ConversationalUI";

// Render while awaiting response
{loading && <LoadingBubble />}

EmptyState

Zero-message state with prompt chips. Shown when the message list is empty. Chips call onChipClick with the chip text.

Ask Meno anything about Cognition

Tokens, components, and the rules that hold the system together.

Prop
Type
Default
Description
onChipClick
(text: string) => void
required
Called with the chip label when a prompt chip is clicked. Wire to ChatShell's handleSend or your own send handler.
import { EmptyState } from "@/components/ConversationalUI";

<EmptyState
  onChipClick={(text) => handleSend(text)}
/>

Don't and Do

Don't

Don't render ChatShell inside a Dialog or Sheet. It is a panel or inline surface — it does not block the canvas behind it. Don't hardcode a system prompt in the call site — pass it as the systemPrompt prop so each context owns its own behaviour.

Do
// Panel — does not block the canvas
<div className="w-96 border-l border-border-default">
  <ChatShell
    systemPrompt="You are analyzing Tower pipeline data."
    placeholder="Ask about this pipeline…"
  />
</div>

// With custom API layer (Tower, Platform)
<ChatShell
  onSend={async (messages, userMessage) => {
    const res = await myProductApi.chat(messages);
    return res.reply;
  }}
/>
import { ChatShell } from "@/components/ConversationalUI";

export function ContextPanel() {
  return (
    <div className="flex h-full flex-col border-l border-border-default">
      <ChatShell
        systemPrompt="You are analyzing Distyl pipeline data."
        placeholder="Ask about this context…"
      />
    </div>
  );
}
Distyl-specific — no fe-distillery counterpart yet. API route lives at app/api/chat/route.ts and requires ANTHROPIC_API_KEY in the environment. Model is claude-sonnet-4-6 with a 1000-token max. Override the API call entirely via the onSend prop.