AI Elements
Chat
The thread. Message Scroller keeps your place while the reply streams.
Chat is the thread of a conversation with the agent. Each turn is a Message inside Message Scroller. The person's message is the scroll anchor, so a new question settles near the top and the reply streams in under it. The field is a Prompt Input.
Installation
Chat is built with the Message Scroller, Message, Bubble, and Input Group components.
Usage
<template>
<UiMessageScrollerProvider auto-scroll>
<UiMessageScroller>
<UiMessageScrollerViewport>
<UiMessageScrollerContent>
<UiMessageScrollerItem :message-id="id" scroll-anchor>
<UiMessage align="end">
<UiMessageContent>
<UiBubble align="end" variant="muted">
<UiBubbleContent>{{ text }}</UiBubbleContent>
</UiBubble>
</UiMessageContent>
</UiMessage>
</UiMessageScrollerItem>
</UiMessageScrollerContent>
</UiMessageScrollerViewport>
<UiMessageScrollerButton />
</UiMessageScroller>
</UiMessageScrollerProvider>
</template>
scroll-anchor goes on the person's turn. auto-scroll follows the reply while the person stays at the live edge.
See Message Scroller.
AI SDK
Render chat.messages from the AI SDK Chat class. Each message is a UiMessageScrollerItem keyed by its id, and the person's messages are the scroll anchors.
<template>
<UiMessageScrollerProvider auto-scroll>
<UiMessageScroller>
<UiMessageScrollerViewport>
<UiMessageScrollerContent :aria-busy="chat.status === 'streaming'">
<UiMessageScrollerItem
v-for="message in chat.messages"
:key="message.id"
:message-id="message.id"
:scroll-anchor="message.role === 'user'"
>
<!-- Render the message -->
</UiMessageScrollerItem>
</UiMessageScrollerContent>
</UiMessageScrollerViewport>
<UiMessageScrollerButton />
</UiMessageScroller>
</UiMessageScrollerProvider>
</template>
<script lang="ts" setup>
import { Chat } from "@ai-sdk/vue";
const chat = new Chat({});
</script>
Markdown
The agent's reply streams as markdown. Render it with Comark (@comark/vue) and route fenced code to a Code Block with a custom pre component, the same way as in Message. Pass streaming only to the reply that is still streaming.
<template>
<UiMessage>
<UiMessageContent>
<Markdown
class="typeset"
:value="getText(message)"
:components="{ pre: MarkdownCodeBlock }"
:streaming="chat.status === 'streaming' && message === chat.messages.at(-1)"
/>
</UiMessageContent>
</UiMessage>
</template>
<script lang="ts" setup>
import { Markdown } from "@comark/vue";
import MarkdownCodeBlock from "./MarkdownCodeBlock.vue";
</script>