Skip to content

Conversation

Agent Composer

Prompt input with Ask / Plan / Act modes, a context tray with token counts and messages that queue while the agent works.

2 variantsMIT

Preview

  • src/i18n/locales/en.json6.2k
  • + 6.2k tokens

files · / commands

Installation

Copies the component into components/ui and installs its dependencies and theme tokens.

pnpm dlx shadcn@latest add https://tuffui.com/r/agent-composer.json

Usage

Pass your own data and wire the callbacks to your agent runtime.

import { useEffect, useState } from "react";
import {
  AgentComposer,
  type ComposerFile,
  type ComposerQueued,
} from "@/components/ui/agent-composer";

export function Composer({ files }: { files: ComposerFile[] }) {
  const { status, send, stop } = useAgentRun(); // your runtime
  const [queue, setQueue] = useState<ComposerQueued[]>([]);

  // When a run ends, send the next queued follow-up.
  useEffect(() => {
    if (status !== "idle" || !queue.length) return;
    send({ text: queue[0].text });
    setQueue(queue.slice(1));
  }, [status, queue]);

  return (
    <AgentComposer
      status={status}
      files={files} // @ mention candidates: { id, path, tokens }
      queue={queue}
      onQueueChange={setQueue}
      onSubmit={({ text, mode, context, model }) => send({ text, mode, context, model })}
      onStop={stop}
      models={[
        { id: "opus-5.5", name: "Claude Opus 5.5" },
        { id: "sonnet-5.5", name: "Claude Sonnet 5.5" },
      ]}
      commands={[{ name: "review", description: "Review the current diff" }]}
    />
  );
}

Props

PropTypeDefaultDescription
onSubmit(message: ComposerMessage) => void—Called on Enter or Send when the agent is idle, with the text, mode, context items and model. The context tray clears afterwards.
status"idle" | "streaming""idle"While streaming, Enter queues the message instead of sending, and a Stop button appears.
queue / onQueueChangeComposerQueued[]—Messages written while the agent works, shown above the input with remove buttons. Send the next one when a run ends.
onStop() => void—Called by the Stop button.
mode / onModeChange"ask" | "plan" | "act""act"The Ask / Plan / Act switch in the header, with a hint for the active mode. Act sends with the brand color.
showModesbooleantrueHide the mode switch; messages are then sent as act.
context / onContextChangeComposerContextItem[]—Files and attachments in the context tray, each with a token count and a running total.
filesComposerFile[]—Candidates for the @ mention menu. Picking one adds it to the tray.
commandsComposerCommand[]—Slash commands. Typing / at the start opens a filterable menu.
models / model / onModelChangeComposerModel[] · string—Shown as the model id in the header; clicking switches to the next model.
value / onValueChangestring—Control the text. Uncontrolled when omitted, like mode, context and queue.
allowAttachmentsbooleantrueShows Add context, which opens a file picker (tokens estimated at 4 bytes each).
placeholderstring—Textarea placeholder.
classNamestring—Extra classes for the root element.
type ComposerMode = "ask" | "plan" | "act";
type ComposerContextItem = { id: string; name: string; tokens: number };
type ComposerFile = { id: string; path: string; tokens: number };
type ComposerQueued = { id: string; text: string };
type ComposerModel = { id: string; name: string; description?: string };
type ComposerCommand = { name: string; description: string };
type ComposerMessage = {
  text: string;
  mode: ComposerMode;
  context: ComposerContextItem[];
  model?: string;
};