Skip to main content
Gremorie

Your first component

Five minute tutorial: build a working chat surface from three primitives.

This tutorial wires three Gremorie AI primitives -- PromptInput, Conversation, and Message -- into a working chat surface. You will end with a UI that accepts a prompt, displays the user message, and streams a model response.

This is the flagship Gremorie composition. The same three primitives power the chat surface block planned in Blocks.

Goal

A chat surface where:

  • The user types a prompt and submits it
  • The user message renders in the conversation
  • An AI message streams back in response

End to end, in five minutes.

Steps

Add the primitives

npx gremorie@latest add ng-prompt-input ng-conversation ng-message
npx gremorie@latest add rx-prompt-input rx-conversation rx-message

The CLI resolves the cross-item dependencies (each primitive depends on *-core for tokens and cn utility), pulls them in too, and writes everything to src/components/gremorie/.

Compose the surface

import { Component, signal } from '@angular/core';
import { NgConversation } from '@/components/gremorie/ng-conversation';
import { NgMessage } from '@/components/gremorie/ng-message';
import { NgPromptInput } from '@/components/gremorie/ng-prompt-input';

interface Msg {
  role: 'user' | 'assistant';
  content: string;
}

@Component({
  standalone: true,
  imports: [NgConversation, NgMessage, NgPromptInput],
  template: `
    <ng-conversation>
      @for (m of messages(); track $index) {
        <ng-message [role]="m.role">{{ m.content }}</ng-message>
      }
    </ng-conversation>
    <ng-prompt-input (submit)="onSubmit($event)" />
  `,
})
export class ChatComponent {
  messages = signal<Msg[]>([]);
  onSubmit(text: string) {
    this.messages.update((m) => [...m, { role: 'user', content: text }]);
    // wire your backend here
  }
}
'use client';
import { useState } from 'react';
import { Conversation } from '@/components/gremorie/rx-conversation';
import { Message } from '@/components/gremorie/rx-message';
import { PromptInput } from '@/components/gremorie/rx-prompt-input';

type Msg = { role: 'user' | 'assistant'; content: string };

export function Chat() {
  const [messages, setMessages] = useState<Msg[]>([]);
  return (
    <>
      <Conversation>
        {messages.map((m, i) => (
          <Message key={i} role={m.role}>
            {m.content}
          </Message>
        ))}
      </Conversation>
      <PromptInput
        onSubmit={(text) => {
          setMessages((prev) => [...prev, { role: 'user', content: text }]);
          // wire your backend here
        }}
      />
    </>
  );
}

Wire a backend

The primitives are transport agnostic. Hook them up to whatever you use: the AI SDK, a Server Action, a streaming endpoint of your own.

// Minimal handler -- replace with your real model call
async function handleSubmit(text: string) {
  setMessages((prev) => [...prev, { role: 'user', content: text }]);
  const response = await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ message: text }),
  });
  const data = await response.json();
  setMessages((prev) => [...prev, { role: 'assistant', content: data.reply }]);
}

For streaming responses, swap the fetch for a streaming reader and append tokens to the assistant message as they arrive.

Run it

nx serve my-app
pnpm dev

Open the page, type a prompt, hit Enter. The user bubble lands in the conversation, your backend responds, the assistant bubble joins it.

See the full wiring in the planned chat-surface block -- it bundles a streaming SDK, attachments, and tool-call rendering.

What you learned

  • Primitives install as source via the CLI
  • Composition over configuration: three small components form a surface
  • The same shape works in Angular and React because the registry is the source of truth

Next, see Project setup for advanced config (path aliases, theme switching, custom token overrides).

On this page