Installation
Complete project setup with vue/reka, then install these components:
pnpm exec frontfriend download message marker button bubble -f vue --primitive reka Restore keeps the checkpoint message. Continue adds a new turn after it. The marker remains visible, and the status text announces the result. In an application, restore the matching application state as well as the messages.
Usage
Complete example
<script setup lang="ts">
import { ref } from "vue";
import { Message } from "@/components/ui/message";
import { MessageContent } from "@/components/ui/message";
import { Bubble } from "@/components/ui/bubble";
import { BubbleContent } from "@/components/ui/bubble";
import { Marker } from "@/components/ui/marker";
import { MarkerContent } from "@/components/ui/marker";
import { Button } from "@/components/ui/button";
const args = {
"label": "Layout saved",
};
const messages = ref([
{ id: "user-1", role: "user", text: "Build the page layout." },
{
id: "assistant-1",
role: "assistant",
text: "The page layout is ready.",
},
{
id: "user-2",
role: "user",
text: "Add a footer with the project links.",
},
{
id: "assistant-2",
role: "assistant",
text: "I added the footer and its links.",
},
]);
const status = ref("");
const continueContainer = ref<HTMLDivElement | null>(null);
function restore() {
messages.value = messages.value.slice(0, 2);
status.value = "Restored. Later turns removed.";
continueContainer.value?.querySelector("button")?.focus();
}
function next() {
messages.value.push(
{
id: crypto.randomUUID(),
role: "user",
text: "Continue from this layout.",
},
{
id: crypto.randomUUID(),
role: "assistant",
text: "I will keep the saved layout and add the next change.",
},
);
status.value = "Added a new exchange.";
}
</script>
<template>
<div
style="
width: 100%;
max-width: 560px;
min-width: 0;
margin-inline: auto;
display: flex;
flex-direction: column;
gap: 20px;
padding-block: 16px;
font-size: var(--text-sm);
line-height: 1.5;
"
>
<template v-for="message in messages" :key="message.id"
><Message :align="message.role === 'user' ? 'end' : 'start'"
><MessageContent
><Bubble v-if="message.role === 'user'" align="end" variant="muted"
><BubbleContent>{{ message.text }}</BubbleContent></Bubble
>
<p v-else>{{ message.text }}</p></MessageContent
></Message
>
<Marker v-if="message.id === 'assistant-1'" variant="separator"
><MarkerContent
><span
style="
display: inline-flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
"
>{{ args.label
}}<Button
v-if="messages.length > 2"
size="xs"
variant="outline"
:aria-label="'Restore to ' + args.label"
@click="restore"
>Restore</Button
></span
></MarkerContent
></Marker
>
</template>
<div ref="continueContainer">
<Button variant="outline" size="sm" @click="next">Continue</Button>
</div>
<p
role="status"
style="
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
"
>
{{ status }}
</p>
</div>
</template> Components and API
This composition has no separate component API. Use the APIs for its installed components.
| Component | Target reference |
|---|---|
message | Reka UI API |
marker | Reka UI API |
button | Reka UI API |
bubble | Reka UI API |