All blocks

AI interfaces / BLOCK

AI Chat

A responsive assistant chat with streaming replies, file attachments, and conversation controls.

AI Chat

A responsive assistant chat with streaming and file attachments.

Aryan

Summarize the main product goals.

ISTMX AI

The product focuses on clear community, messaging, creation, and discovery flows.

Overview

Bring your own model handler and connect it to a ready-made chat workspace. The block handles the responsive conversation UI, streamed text, attachment previews, cancellation, and local chat history.

Features

  • Provider-agnostic onSend handler accepts a string or an async stream.
  • Streams plain text and code without rendering unfinished Markdown fences as raw text.
  • Lets users stop generation, retry errors, and continue typing while a response streams.
  • Supports image, document, audio, video, and custom attachment options.
  • Includes local chat history with pin, archive, restore, and delete actions.
  • Provides a collapsible desktop sidebar and a closed-by-default mobile drawer.
  • Includes optional voice input and configurable accent color, attachment count, and response size.

Installation

Choose a package manager to add the complete editable block to components/ai-chat-block. The CLI installs its runtime dependencies and keeps the helper files inside that folder.

Package manager
$ npx @istmx/ui add ai-chat-block

Copies the install command for your selected package manager.

React and Tailwind CSS should already be configured in your app. The command installs Motion, Tabler Icons, clsx, and tailwind-merge when they are missing.

Dependencies

motion@tabler/icons-reactclsxtailwind-merge

Files included

The installer creates one self-contained folder. Each file has one clear role, so you can customize the block without tracking down portfolio-specific imports.

components/ai-chat-block/
components/ai-chat-block/
├── index.ts
├── ai-chat-block.tsx
├── ai-chat-input.tsx
├── chat-composer.tsx
├── chat-sidebar.tsx
├── message-list.tsx
├── chat-message.tsx
├── message-content.tsx
├── chat-code-block.tsx
├── code-highlighter.tsx
├── attachment.tsx
├── default-attachments.tsx
├── send-button.tsx
├── voice-button.tsx
├── chat-logo.tsx
├── chat-user-avatar.tsx
├── types.ts
├── utils.ts
index.ts

Public component and type exports.

ai-chat-block.tsx

Main block, local conversation state, model request lifecycle, streaming, retry, and stop behavior.

ai-chat-input.tsx

Message field, attachment selection and previews, voice input, and send controls.

chat-composer.tsx

Connects the main block to the message input and forwards composer events.

chat-sidebar.tsx

Conversation list, new chat, pin, archive, restore, delete, and account area.

message-list.tsx

Scrollable conversation log and empty state.

chat-message.tsx

User and assistant message layout, thinking disclosure, files, and errors.

message-content.tsx

Renders paragraphs, headings, lists, inline formatting, and live code fences.

chat-code-block.tsx

Finished code presentation and copy action.

code-highlighter.tsx

Small dependency-free tokenizer used by the code block.

attachment.tsx

Attachment picker dialog, file preview tiles, and enlarged previews.

default-attachments.tsx

Default image, document, audio, and video options.

send-button.tsx

Animated send, sending, stop, and sent states.

voice-button.tsx

Optional browser voice-input control.

chat-logo.tsx

Inline SVG mark used by the assistant and sidebar.

chat-user-avatar.tsx

Fallback user avatar; pass userAvatar to show your own profile image.

types.ts

Public request, response, thread, message, and attachment types.

utils.ts

Local Tailwind class merging helper; no shared lib/utils file is required.

Connect your chat handler

Import the component and pass an onSend function. It receives the message, selected File objects, and an AbortSignal. Keep provider credentials and model calls in your server endpoint.

support-chat.tsx
import { AiChatBlock } from '@/components/ai-chat-block'
import type { AiChatBlockProps } from '@/components/ai-chat-block'
 
const onSend: AiChatBlockProps['onSend'] = async ({
message,
attachments,
signal,
}) => {
const body = new FormData()
body.set('message', message)
attachments.forEach(({ file }) => body.append('files', file))
 
const response = await fetch('/api/chat', {
method: 'POST',
body,
signal,
})
 
if (!response.ok) throw new Error('The chat request failed.')
const result = (await response.json()) as { answer: string }
return result.answer
}
 
export function SupportChat() {
return (
<div className="mx-auto w-full max-w-5xl">
<AiChatBlock
title="Support assistant"
assistantName="ISTMX AI"
userName="You"
accentColor="#0369a1"
maxResponseCharacters={40_000}
onSend={onSend}
/>
</div>
)
}

Return a live response

Return an async iterable of text chunks to stream a response. The block renders text as it arrives and passes the same abort signal through so Stop can cancel your provider request.

streaming-handler.ts
import type { AiChatBlockProps } from '@/components/ai-chat-block'
 
async function* decodeTextStream(
body: ReadableStream<Uint8Array>,
): AsyncGenerator<string> {
const reader = body.getReader()
const decoder = new TextDecoder()
 
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
const text = decoder.decode(value, { stream: true })
if (text) yield text
}
const remaining = decoder.decode()
if (remaining) yield remaining
} finally {
reader.releaseLock()
}
}
 
const onSend: AiChatBlockProps['onSend'] = async ({ message, attachments, signal }) => {
const body = new FormData()
body.set('message', message)
attachments.forEach(({ file }) => body.append('files', file))
const response = await fetch('/api/chat/stream', { method: 'POST', body, signal })
if (!response.ok) throw new Error('The chat request failed.')
return response.body ? decodeTextStream(response.body) : response.text()
}

Adapt your SDK’s chunk shape to strings or ChatStreamChunk objects before yielding. For a short visible activity status, yield a thinking-delta chunk; do not forward private model reasoning.

Configure attachments

Use the default image, document, audio, and video options, or pass your own list to control accepted file types and selection behavior.

attachment-options.tsx
import {
AiChatBlock,
DEFAULT_ATTACHMENT_OPTIONS,
} from '@/components/ai-chat-block'
import type { AiChatBlockProps, AttachmentOption } from '@/components/ai-chat-block'
 
const attachmentOptions: AttachmentOption[] =
DEFAULT_ATTACHMENT_OPTIONS.filter((option) => option.id !== 'video')
 
export function SupportChat({ onSend }: { onSend: AiChatBlockProps['onSend'] }) {
return (
<AiChatBlock
attachmentOptions={attachmentOptions}
maxAttachments={5}
maxFileSizeBytes={10 * 1024 * 1024}
onSend={onSend}
/>
)
}

How it works

The onSend callback receives the message, selected attachments, and an AbortSignal. It can return a complete string or an async iterable of text chunks. Yield thinking-delta or reasoning-delta chunks only when you have a short, user-facing activity update to show.

Composition

AI Chat block
AiChatBlock
├── ChatSidebar
│ ├── Pinned conversations
│ ├── Recent conversations
│ └── Account profile
├── MessageList
│ └── ChatMessage
│ ├── ThinkingDisclosure
│ ├── MessageContent
│ └── AttachmentPreviews
└── ChatComposer
├── AttachmentDialog
├── VoiceButton
└── SendButton / Stop

API reference

onSend(request: ChatRequest) => Promise<ChatResponse>

Required. Connect your model or API and return a string or async text stream.

initialMessages?readonly ChatTurn[]

Messages for the first local conversation.

initialThreads?readonly ChatThread[]

Optional local chat history to show in the sidebar.

title?string

Heading displayed in the chat header.

description?string

Short context shown beneath the chat heading.

assistantName? / userName?string

Names displayed alongside each message and in the account area.

assistantAvatar? / userAvatar?ReactNode

Optional custom avatar content for assistant and user messages.

attachmentOptions?readonly AttachmentOption[]

File types and selection behavior offered by the plus menu.

maxAttachments?number

Maximum files per message. Defaults to 10.

maxFileSizeBytes?number

Optional per-file size limit.

maxResponseCharacters?number

Maximum streamed response characters. Defaults to 60,000.

accentColor?string

CSS color used for focus and active accents.

allowVoiceInput?boolean

Shows the optional voice input control.

placeholder? / emptyMessage?string

Composer placeholder and empty conversation guidance.

onListeningChange?(listening: boolean) => void

Notifies the host when voice input starts or stops.

className?string

Additional classes for sizing and layout.

The listed props reflect the current block interface.

Accessibility and limitations

Uses native controls and labels, supports keyboard operation, exposes streaming status, respects reduced motion, and keeps attachment and chat-history actions available without hover on touch screens.

Conversation history is held in component state and is cleared when the page reloads. Connect a server-side store to persist conversations. Model credentials and provider calls belong in your application handler, not in the browser-facing component.