Skip to content
edmi-ui

Checkpoint

Marks a point in the conversation with a restore action.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
14:02
14:02
14:02

Built on button, separator and tooltip.

Terminal window
npx shadcn@latest add @edmi-ui/ai-checkpoint
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/ai-checkpoint
Terminal window
yarn dlx shadcn@latest add @edmi-ui/ai-checkpoint
Terminal window
bunx shadcn@latest add @edmi-ui/ai-checkpoint

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/ai-checkpoint
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/ai-checkpoint
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/ai-checkpoint
Terminal window
bunx shadcn-vue@latest add @edmi-ui/ai-checkpoint

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-checkpoint.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-checkpoint.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-checkpoint.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/ai-checkpoint.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

import {
Checkpoint,
CheckpointIcon,
CheckpointTrigger,
} from "@/components/ai/checkpoint";
<Checkpoint className="w-full max-w-xl" time="14:02">
<CheckpointIcon />
<CheckpointTrigger tooltip="Restore the conversation and the agent's changes to this point">
<ArrowCounterClockwiseIcon />
Restore checkpoint
</CheckpointTrigger>
</Checkpoint>
<script setup lang="ts">
import {
Checkpoint,
CheckpointIcon,
CheckpointTrigger,
} from "@/components/ai/checkpoint";
</script>
<template>
<Checkpoint class="w-full max-w-xl" time="14:02">
<CheckpointIcon />
<CheckpointTrigger tooltip="Restore the conversation and the agent's changes to this point">
<RotateCwIcon class="-scale-x-100" />
Restore checkpoint
</CheckpointTrigger>
</Checkpoint>
</template>
<script lang="ts">
import {
Checkpoint,
CheckpointIcon,
CheckpointTrigger,
} from "$lib/components/ai/checkpoint";
</script>
<Checkpoint class="w-full max-w-xl" time="14:02">
<CheckpointIcon />
<CheckpointTrigger tooltip="Restore the conversation and the agent's changes to this point">
<ArrowCounterClockwiseIcon />
Restore checkpoint
</CheckpointTrigger>
</Checkpoint>
registry/components/ai/checkpoint.tsx
"use client";
import { cn } from "cn";
import type { ComponentProps, HTMLAttributes, ReactNode } from "react";
import { BookmarkIcon } from "@phosphor-icons/react";
import { Button } from "@/registry/edmi/ui/button";
import { Separator } from "@/registry/edmi/ui/separator";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/registry/edmi/ui/tooltip";
export type CheckpointProps = HTMLAttributes<HTMLDivElement> & {
/** ✦ Mono timestamp (or any label) after the line. */
time?: ReactNode;
};
export const Checkpoint = ({
className,
children,
time,
...props
}: CheckpointProps) => (
<div
data-slot="ai-checkpoint"
className={cn(
"flex items-center gap-2.5 overflow-hidden text-muted-foreground",
className,
)}
{...props}
>
{children}
<Separator className="flex-1" />
{time && (
<span className="shrink-0 font-mono text-[11.5px] text-muted-foreground">
{time}
</span>
)}
</div>
);
export type CheckpointIconProps = ComponentProps<"span">;
export const CheckpointIcon = ({
className,
children,
...props
}: CheckpointIconProps) => (
<span
data-slot="ai-checkpoint-icon"
className={cn("inline-flex shrink-0 [&_svg]:size-4", className)}
{...props}
>
{children ?? (
<BookmarkIcon
/>
)}
</span>
);
export type CheckpointTriggerProps = ComponentProps<typeof Button> & {
tooltip?: string;
};
export const CheckpointTrigger = ({
children,
variant = "ghost",
size = "xs",
tooltip,
...props
}: CheckpointTriggerProps) =>
tooltip ? (
<Tooltip>
<TooltipTrigger
render={
<Button
data-slot="ai-checkpoint-trigger"
size={size}
type="button"
variant={variant}
{...props}
/>
}
>
{children}
</TooltipTrigger>
<TooltipContent align="start" side="bottom">
{tooltip}
</TooltipContent>
</Tooltip>
) : (
<Button
data-slot="ai-checkpoint-trigger"
size={size}
type="button"
variant={variant}
{...props}
>
{children}
</Button>
);
registry/components/ai/checkpoint/Checkpoint.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/registry/edmi/lib/utils"
import { Separator } from "@/registry/edmi/ui/separator"
const props = defineProps<{
class?: HTMLAttributes["class"]
/** ✦ Mono timestamp (or any label) after the line; the `time` slot works too. */
time?: string
}>()
</script>
<template>
<div
data-slot="ai-checkpoint"
:class="cn('flex items-center gap-2.5 overflow-hidden text-muted-foreground', props.class)"
>
<slot />
<Separator class="flex-1" />
<span v-if="time || $slots.time" class="shrink-0 font-mono text-[11.5px] text-muted-foreground">
<slot name="time">{{ time }}</slot>
</span>
</div>
</template>
registry/components/ai/checkpoint/CheckpointIcon.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { BookmarkIcon } from "@lucide/vue"
import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<span data-slot="ai-checkpoint-icon" :class="cn('inline-flex shrink-0 [&_svg]:size-4', props.class)">
<slot><BookmarkIcon /></slot>
</span>
</template>
registry/components/ai/checkpoint/CheckpointTrigger.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { ButtonVariants } from "@/registry/edmi/ui/button"
import { Button } from "@/registry/edmi/ui/button"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/registry/edmi/ui/tooltip"
const props = withDefaults(defineProps<{
tooltip?: string
variant?: ButtonVariants["variant"]
size?: ButtonVariants["size"]
class?: HTMLAttributes["class"]
}>(), {
variant: "ghost",
size: "xs",
})
</script>
<template>
<TooltipProvider v-if="props.tooltip">
<Tooltip>
<TooltipTrigger as-child>
<Button data-slot="ai-checkpoint-trigger" type="button" :variant="props.variant" :size="props.size" :class="props.class">
<slot />
</Button>
</TooltipTrigger>
<TooltipContent align="start" side="bottom">
<p>{{ props.tooltip }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Button v-else data-slot="ai-checkpoint-trigger" type="button" :variant="props.variant" :size="props.size" :class="props.class">
<slot />
</Button>
</template>
registry/components/ai/checkpoint/index.ts
export { default as Checkpoint } from "./Checkpoint.vue";
export { default as CheckpointIcon } from "./CheckpointIcon.vue";
export { default as CheckpointTrigger } from "./CheckpointTrigger.vue";
src/lib/registry/ai/checkpoint/checkpoint-icon.svelte
<script lang="ts">
import BookmarkIcon from "phosphor-svelte/lib/Bookmark";
import { cn } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
let { class: className, children, ...restProps }: HTMLAttributes<HTMLSpanElement> = $props();
</script>
<span data-slot="ai-checkpoint-icon" class={cn("inline-flex shrink-0 [&_svg]:size-4", className)} {...restProps}>
{#if children}
{@render children()}
{:else}
<BookmarkIcon
/>
{/if}
</span>
src/lib/registry/ai/checkpoint/checkpoint-trigger.svelte
<script lang="ts">
import { Button, type ButtonProps } from "$lib/registry/ui/button/index.js";
import * as Tooltip from "$lib/registry/ui/tooltip/index.js";
let {
tooltip,
variant = "ghost",
size = "xs",
children,
...restProps
}: Omit<ButtonProps, "href"> & { tooltip?: string } = $props();
</script>
{#if tooltip}
<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger>
{#snippet child({ props })}
<Button {...props} data-slot="ai-checkpoint-trigger" type="button" {variant} {size} {...restProps}>
{@render children?.()}
</Button>
{/snippet}
</Tooltip.Trigger>
<Tooltip.Content align="start" side="bottom">
<p>{tooltip}</p>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{:else}
<Button data-slot="ai-checkpoint-trigger" type="button" {variant} {size} {...restProps}>
{@render children?.()}
</Button>
{/if}
src/lib/registry/ai/checkpoint/checkpoint.svelte
<script lang="ts">
import { cn } from "$lib/utils.js";
import { Separator } from "$lib/registry/ui/separator/index.js";
import type { Snippet } from "svelte";
import type { HTMLAttributes } from "svelte/elements";
let {
class: className,
time,
children,
...restProps
}: Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
/** ✦ Mono timestamp (or any label) after the line. */
time?: string | Snippet;
children?: Snippet;
} = $props();
</script>
<div
data-slot="ai-checkpoint"
class={cn("flex items-center gap-2.5 overflow-hidden text-muted-foreground", className)}
{...restProps}
>
{@render children?.()}
<Separator class="flex-1" />
{#if time}
<span class="shrink-0 font-mono text-[11.5px] text-muted-foreground">
{#if typeof time === "string"}{time}{:else}{@render time()}{/if}
</span>
{/if}
</div>
src/lib/registry/ai/checkpoint/index.ts
import Root from "./checkpoint.svelte";
import Icon from "./checkpoint-icon.svelte";
import Trigger from "./checkpoint-trigger.svelte";
export {
Icon,
Icon as CheckpointIcon,
Root,
//
Root as Checkpoint,
Trigger,
Trigger as CheckpointTrigger,
};
Part Prop Type Default
Checkpoint time ReactNode (mono label after the line)
CheckpointIcon children custom icon bookmark
CheckpointTrigger tooltip, variant, size Button props ghost, xs
  • time ✦ puts a mono timestamp after the line.
  • The trigger defaults to a ghost xs button with a tooltip.