Skip to main content
Gremorie

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

What is Gremorie?

Gremorie is an AI-native design system: registry + MCP first, React and Angular bindings second.

How do I install a primitive?

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-bottom

Instalaçã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>

PropTypeDefaultDescription
classNamestring-Classes extras no conteúdo com scroll.

Renderiza dentro de StickToBottom.Content. Adiciona uma pilha flex vertical com gap-8 e p-4.

<ConversationScrollButton>

PropTypeDefaultDescription
classNamestring-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>

PropTypeDefaultDescription
titlestring"No messages yet"Título renderizado quando não há conteúdo.
descriptionstring"Start a conversation to see messages here"Texto de apoio abaixo do título.
iconReactNode-Ícone opcional acima do título.
childrenReactNode-Quando fornecido, substitui completamente o layout padrão de título / descrição.

Composição

  1. <Conversation> detém o container de scroll e o role log.
  2. <ConversationContent> é a coluna flex interna onde os irmãos Message vivem.
  3. <ConversationScrollButton> flutua dentro da conversa, mostrado somente enquanto o usuário não está no fim.
  4. <ConversationEmptyState> pode substituir ConversationContent (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: ConversationScrollButton só 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

On this page