Conversation
Container com scroll-to-bottom que mantém a mensagem mais recente à vista enquanto o conteúdo faz streaming.
Visão geral
Conversation é a casca externa de qualquer superfície de chat. Ele envolve use-stick-to-bottom para que a posição do scroll siga as novas mensagens conforme elas chegam, e então sai do caminho no momento em que o usuário rola para cima para ler o histórico. O ConversationScrollButton correspondente flutua para trazer o usuário de volta ao fim sob demanda.
Use como pai de qualquer stream de Message. Use ConversationEmptyState para o caso de zero mensagens.
Preview
Padrão
Gremorie is an AI-native design system: registry + MCP first, React and Angular bindings second.
Use npx gremorie add rx-message. The registry resolves dependencies and writes source files into your project.
'use client';import { Conversation, ConversationContent, Message, MessageContent, MessageResponse,} from '@gremorie/rx-ai';export function ConversationPreview() { return ( <div className="h-[320px] rounded-lg border"> <Conversation> <ConversationContent> <Message from="user"> <MessageContent>What is Gremorie?</MessageContent> </Message> <Message from="assistant"> <MessageContent> <MessageResponse> Gremorie is an AI-native design system: registry + MCP first, React and Angular bindings second. </MessageResponse> </MessageContent> </Message> <Message from="user"> <MessageContent>How do I install a primitive?</MessageContent> </Message> <Message from="assistant"> <MessageContent> <MessageResponse> Use `npx gremorie add rx-message`. The registry resolves dependencies and writes source files into your project. </MessageResponse> </MessageContent> </Message> </ConversationContent> </Conversation> </div> );}Estado vazio
Start a conversation
Ask anything about Gremorie primitives, tokens, or the registry.
'use client';import { Conversation, ConversationContent, ConversationEmptyState,} from '@gremorie/rx-ai';import { MessageCircleQuestion } from 'lucide-react';export function ConversationEmptyPreview() { return ( <div className="h-[240px] rounded-lg border"> <Conversation> <ConversationContent> <ConversationEmptyState icon={<MessageCircleQuestion className="size-8" />} title="Start a conversation" description="Ask anything about Gremorie primitives, tokens, or the registry." /> </ConversationContent> </Conversation> </div> );}Anatomia
Conversation
├─ ConversationContent
│ ├─ ConversationEmptyState mostrado quando vazio
│ └─ Message … as mensagens (veja Message)
└─ ConversationScrollButton scroll-to-bottomInstalação
bash npx gremorie@latest add rx-conversation bash pnpm dlx gremorie@latest add rx-conversation
bash yarn dlx gremorie@latest add rx-conversation
bash bunx --bun gremorie@latest add rx-conversation
Uso
import {
Conversation,
ConversationContent,
ConversationScrollButton,
} from "@gremorie/rx-ai";
export function Example() {
return (
<Conversation>
<ConversationContent>
{messages.map((m) => (
<Message key={m.id} from={m.role}>
<MessageContent>{m.text}</MessageContent>
</Message>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
);
}import { Component, signal } from "@angular/core";
import {
Conversation,
ConversationContent,
ConversationScrollButton,
Message,
MessageContent,
} from "@gremorie/ng-ai";
@Component({
selector: "app-example",
standalone: true,
imports: [
Conversation,
ConversationContent,
ConversationScrollButton,
Message,
MessageContent,
],
template: ` <conversation class="h-96 w-full">
<conversation-content>
@for (m of messages(); track m.id) {
<message [from]="m.role">
<message-content>{{ m.text }}</message-content>
</message>
}
</conversation-content>
<conversation-scroll-button />
</conversation>
`,
})
export class ExampleComponent {
readonly messages = signal<Array<{ id: string; role: "user" | "assistant"; text: string }>>([]);
}
API
<Conversation>
Encaminha todas as props de StickToBottom do use-stick-to-bottom. O comportamento sticky é pré-configurado (initial="smooth", resize="smooth") e o elemento carrega role="log" para tecnologias assistivas.
<ConversationContent>
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Classes extras no conteúdo com scroll. |
Renderiza dentro de StickToBottom.Content. Adiciona uma pilha flex vertical com gap-8 e p-4.
<ConversationScrollButton>
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Classes extras no botão. |
Encaminha todas as props de Button. Só renderiza quando o usuário rolou para longe do fim; clicar rola de volta suavemente.
<ConversationEmptyState>
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "No messages yet" | Título renderizado quando não há conteúdo. |
description | string | "Start a conversation to see messages here" | Texto de apoio abaixo do título. |
icon | ReactNode | - | Ícone opcional acima do título. |
children | ReactNode | - | Quando fornecido, substitui completamente o layout padrão de título / descrição. |
Composição
<Conversation>detém o container de scroll e o rolelog.<ConversationContent>é a coluna flex interna onde os irmãosMessagevivem.<ConversationScrollButton>flutua dentro da conversa, mostrado somente enquanto o usuário não está no fim.<ConversationEmptyState>pode substituirConversationContent(ou ser renderizado como irmão) quando não há nada para mostrar.
Variações
Conversa com streaming padrão
O caso de 90%: alimente as mensagens e deixe Conversation manter a mais recente à vista.
<Conversation className="h-[600px]">
<ConversationContent>
{messages.map((m) => (
<Message key={m.id} from={m.role}>
<MessageContent>
<MessageResponse>{m.text}</MessageResponse>
</MessageContent>
</Message>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>Estado vazio antes da primeira mensagem
Mostre isto em uma thread nova para convidar ao primeiro prompt.
<Conversation className="h-[600px]">
{messages.length === 0 ? (
<ConversationEmptyState
icon={<MessageCircleIcon className="size-8" />}
title="Ask anything"
description="The assistant will answer with sources and a plan."
/>
) : (
<ConversationContent>
{messages.map((m) => (
<Message key={m.id} from={m.role}>
<MessageContent>{m.text}</MessageContent>
</Message>
))}
</ConversationContent>
)}
<ConversationScrollButton />
</Conversation>Corpo de estado vazio customizado
Use children para controlar totalmente o layout vazio mantendo o container centralizado.
<ConversationEmptyState>
<div className="space-y-3 text-center">
<h3 className="font-medium">Welcome back</h3>
<p className="text-muted-foreground text-sm">Pick up where you left off.</p>
<Button variant="outline">Resume thread</Button>
</div>
</ConversationEmptyState>Acessibilidade
- Teclado: teclas padrão de scroll (Page Up / Page Down, setas, Home / End) funcionam no container de scroll.
- ARIA: a raiz tem
role="log", o que sinaliza às tecnologias assistivas que novas entradas são anexadas no fim e devem ser anunciadas conforme chegam. - Leitores de tela: o estado vazio usa título semântico + parágrafo para ser anunciado como um único bloco, não como texto espalhado.
- Gestão de foco:
ConversationScrollButtonsó monta enquanto há para onde rolar, então não rouba a ordem de Tab em uma visão totalmente rolada.
Relacionados
- Message - turno atômico renderizado dentro de
ConversationContent - PromptInput - doca de input tipicamente renderizada abaixo da conversa
- Suggestion - chips de quick-reply que frequentemente acompanham o estado vazio