Elements

93 / 122 · Thread

Connection state

The socket drops, the run keeps going on the server, and the stream is picked back up.

Connection lost. The run kept going on the server.

Installation

npx shadcn@latest add "@assistant-ui/elements-connection-state"

Usage

import { ConnectionState } from "@/components/elements/connection-state";

<ConnectionState phase="reconnecting" attempt={2} onRetry={reconnect} />

Props

phase*"online" | "dropped" | "reconnecting" | "resumed"Which banner to show. online renders nothing, so the element can stay mounted.
attemptnumberRetry number, shown while reconnecting.
resumedTokensnumberHow much of the stream was recovered, shown once resumed.
onRetry() => voidCalled from the Reconnect button while dropped.
classNamestringExtra classes merged onto the root.

Source

connection-state.tsx
"use client";

import { CheckIcon, CloudOffIcon, Loader2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
import { mono, paper } from "./surfaces";

export type ConnectionPhase = "online" | "dropped" | "reconnecting" | "resumed";

export function ConnectionState({
  phase,
  attempt,
  resumedTokens,
  onRetry,
  className,
}: {
  phase: ConnectionPhase;
  attempt?: number;
  resumedTokens?: number;
  onRetry?: () => void;
  className?: string;
}) {
  if (phase === "online") return null;

  return (
    <div
      className={cn(
        paper,
        "fade-in slide-in-from-top-1 animate-in flex w-full max-w-sm items-center gap-2.5 rounded-2xl px-3.5 py-2.5 duration-300",
        className,
      )}
    >
      {phase === "dropped" && (
        <>
          <CloudOffIcon className="size-3.5 shrink-0 text-amber-600 dark:text-amber-400" />
          <span className="min-w-0 flex-1 text-[13px]">
            Connection lost. The run kept going on the server.
          </span>
          <button
            type="button"
            onClick={onRetry}
            className="text-foreground/70 hover:bg-foreground/[0.06] hover:text-foreground/95 shrink-0 rounded-full px-2.5 py-1 text-xs font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]"
          >
            Reconnect
          </button>
        </>
      )}

      {phase === "reconnecting" && (
        <>
          <Loader2Icon className="text-foreground/40 size-3.5 shrink-0 animate-spin motion-reduce:animate-none" />
          <span className="min-w-0 flex-1 text-[13px]">Reconnecting</span>
          {attempt !== undefined && (
            <span
              className={cn(mono, "text-foreground/30 shrink-0 tabular-nums")}
            >
              attempt {attempt}
            </span>
          )}
        </>
      )}

      {phase === "resumed" && (
        <>
          <CheckIcon className="size-3.5 shrink-0 text-emerald-500" />
          <span className="min-w-0 flex-1 text-[13px]">
            Picked the stream back up.
          </span>
          {resumedTokens !== undefined && (
            <span
              className={cn(mono, "text-foreground/30 shrink-0 tabular-nums")}
            >
              +{resumedTokens} tokens
            </span>
          )}
        </>
      )}
    </div>
  );
}