Elements

94 / 122 · Thread

Shared conversation

A read-only transcript someone sent you, with a way to pick it up yourself.

Draft restore across threadsshared by harry · 2 days ago
Why did the draft survive the thread switch?
The composer read a slot keyed by the old thread. Clearing it on switch fixes it at the source.
read only

Installation

npx shadcn@latest add "@assistant-ui/elements-shared-conversation"

Usage

import { SharedConversation } from "@/components/elements/shared-conversation";

<SharedConversation
  title="Draft restore across threads"
  sharedBy="harry"
  sharedAt="2 days ago"
  turns={turns}
  onContinue={fork}
/>

Props

title*stringName of the shared thread.
sharedBy*stringWho shared it.
sharedAt*stringPre-formatted share time.
turns*SharedTurn[]The transcript, read only.
onContinue() => voidCalled to fork the transcript into the reader's own thread.
classNamestringExtra classes merged onto the root.

Source

shared-conversation.tsx
"use client";

import { LinkIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { inkButton, mono, paper } from "./surfaces";

export interface SharedTurn {
  id: string;
  role: "user" | "assistant";
  text: string;
}

export function SharedConversation({
  title,
  sharedBy,
  sharedAt,
  turns,
  onContinue,
  className,
}: {
  title: string;
  sharedBy: string;
  sharedAt: string;
  turns: readonly SharedTurn[];
  onContinue?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col overflow-hidden rounded-2xl",
        className,
      )}
    >
      <div className="flex items-center gap-2.5 px-4 pt-3.5 pb-3">
        <LinkIcon className="text-foreground/30 size-3.5 shrink-0" />
        <div className="flex min-w-0 flex-1 flex-col">
          <span className="truncate text-[13.5px] font-medium">{title}</span>
          <span className={cn(mono, "text-foreground/30 truncate")}>
            shared by {sharedBy} · {sharedAt}
          </span>
        </div>
      </div>

      <div className="border-foreground/[0.07] flex flex-col gap-2.5 border-t px-4 py-3.5">
        {turns.map((turn) => (
          <div
            key={turn.id}
            className={cn(
              "text-[13px] leading-relaxed",
              turn.role === "user"
                ? "bg-foreground/[0.05] ms-auto max-w-[85%] rounded-2xl px-3 py-2"
                : "text-foreground/70",
            )}
          >
            {turn.text}
          </div>
        ))}
      </div>

      <div className="border-foreground/[0.07] flex items-center gap-2 border-t px-4 py-3">
        <span className={cn(mono, "text-foreground/30")}>read only</span>
        <button
          type="button"
          onClick={onContinue}
          className={cn(
            inkButton,
            "ms-auto flex h-8 items-center rounded-full px-3.5 text-xs font-medium",
          )}
        >
          Continue in your own chat
        </button>
      </div>
    </div>
  );
}