DHxWhy

AI Agent Tool Call Timeline Chat Input

AI chats 24

About

AI Agent Tool Call Timeline Chat Input for React and Tailwind CSS (shadcn/ui) shows tool progress and retries. It is built to make agent work easy to follow. Includes live timers, results, a summary that collapses or stays open, and streaming answers. Controlled props and callbacks connect your agent. Supports keyboard input, reduced motion and light/dark themes. Use it for AI chat and assistants. The included demo uses sample data; the component makes no requests. Requires React and lucide-react, with no animation library.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds AI Agent Tool Call Timeline Chat Input to your project and fits it to your theme.

Code

How AI Agent Tool Call Timeline Chat Input is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import { RotateCcw } from "lucide-react";
import { AgentToolTimelineChatInput } from "@/components/ui/agent-tool-timeline-chat-input";
import type { AgentToolCall, AgentToolStatus } from "@/components/ui/agent-tool-timeline-chat-input";

const TASK = "Review the sample release plan";
const RESULT = "Checks passed after one retry. The plan is ready for review.";
const SEARCH: AgentToolCall = { id: "search", tool: "search", title: "Find release guidance", status: "queued", detail: "3 sample references matched the release checklist." };
const READ: AgentToolCall = { id: "read", tool: "read", title: "Read launch-notes.md", status: "running", detail: "version: 0.8 · owner: not set · checks: 6" };
const CODE: AgentToolCall = { id: "validate", tool: "code", title: "Validate configuration", status: "running" };
const RETRY: AgentToolCall = { id: "retry", tool: "code", title: "Validate with a fallback", status: "running", retryOf: "validate", detail: "owner: sample-team\n6 / 6 checks passed" };
const completed = (): AgentToolCall[] => [
  { ...SEARCH, status: "done", durationMs: 700 },
  { ...READ, status: "done", durationMs: 900 },
  { ...CODE, status: "error", durationMs: 700, detail: "Missing field: release.owner" },
  { ...RETRY, status: "done", durationMs: 800 },
];
type DemoState = { status: AgentToolStatus; prompt: string; value: string; calls: AgentToolCall[]; answer: string };
const blank = (): DemoState => ({ status: "idle", prompt: "", value: "", calls: [], answer: "" });

export default function AgentToolTimelineChatInputDemo({ autoplay = true, loop = true }: { autoplay?: boolean; loop?: boolean }) {
  const [state, setState] = React.useState(blank);
  const [motion, setMotion] = React.useState({ ready: false, reduced: true });
  const [session, setSession] = React.useState({ id: 0, manual: false, prompt: TASK });
  const [paused, setPaused] = React.useState(false);
  const [activityOpen, setActivityOpen] = React.useState(false);
  const interacted = React.useRef(false);
  React.useEffect(() => {
    const media = window.matchMedia("(prefers-reduced-motion: reduce)");
    const update = () => setMotion({ ready: true, reduced: media.matches });
    update(); media.addEventListener("change", update);
    return () => media.removeEventListener("change", update);
  }, []);
  React.useEffect(() => {
    if (!motion.ready || (!session.manual && (!autoplay || motion.reduced || paused))) return;
    const response = session.prompt === TASK ? RESULT : "Sample tool sequence completed after a retry.\nConnect your agent to provide a response to this request.";
    if (motion.reduced) { setState({ status: "done", prompt: session.prompt, value: "", calls: completed(), answer: response }); return; }
    const timers: number[] = [];
    let stream = 0;
    const later = (time: number, action: () => void) => timers.push(window.setTimeout(action, time));
    const offset = session.manual ? 0 : 900;
    setActivityOpen(false);
    if (!session.manual) {
      setState(blank());
      for (let count = 1; count <= TASK.length; count++) later(200 + count / TASK.length * 600, () => setState((old) => ({ ...old, value: TASK.slice(0, count) })));
    }
    later(offset, () => setState({ status: "running", prompt: session.prompt, value: "", calls: [], answer: "" }));
    const show = (calls: AgentToolCall[]) => setState((old) => ({ ...old, calls }));
    later(offset + 120, () => show([{ ...SEARCH }]));
    later(offset + 250, () => show([{ ...SEARCH, status: "running" }]));
    later(offset + 800, () => show([{ ...SEARCH, status: "done", durationMs: 700 }, { ...READ }]));
    later(offset + 1500, () => show([{ ...SEARCH, status: "done", durationMs: 700 }, { ...READ, status: "done", durationMs: 900 }, { ...CODE }]));
    later(offset + 2050, () => show(completed().slice(0, 3)));
    later(offset + 2350, () => show([...completed().slice(0, 3), { ...RETRY }]));
    later(offset + 3000, () => show(completed()));
    later(offset + 3300, () => {
      setState((old) => ({ ...old, status: "answering" }));
      let cursor = 0;
      stream = window.setInterval(() => {
        cursor += 3;
        setState((old) => ({ ...old, answer: response.slice(0, cursor) }));
        if (cursor >= response.length) window.clearInterval(stream);
      }, 32);
    });
    later(offset + 4300, () => { window.clearInterval(stream); setState((old) => ({ ...old, status: "done", answer: response })); });
    if (session.prompt === TASK) later(offset + 4600, () => { if (!interacted.current) setActivityOpen(true); });
    if (loop && !session.manual) later(11000, () => { if (!interacted.current) setSession((old) => ({ ...old, id: old.id + 1 })); });
    return () => { timers.forEach((timer) => window.clearTimeout(timer)); window.clearInterval(stream); };
  }, [autoplay, loop, motion.ready, motion.reduced, paused, session]);

  function send(prompt: string) {
    setPaused(false); setSession((old) => ({ id: old.id + 1, manual: true, prompt }));
  }
  return (
    <main className="flex min-h-screen w-full items-center justify-center bg-background p-5 font-sans text-foreground">
      <div className="w-full max-w-[480px]">
        <header className="mb-3 flex items-end justify-between gap-3">
          <div><p className="mb-1 text-[9px] font-medium uppercase tracking-[0.18em] text-muted-foreground">Tool call timeline</p><h1 className="text-xl font-medium leading-7 tracking-tight">Watch the work take shape.</h1></div>
          <span className="mb-1 rounded border border-border px-2 py-0.5 text-[9px] text-muted-foreground">Sample agent</span>
        </header>
        <div data-demo-stage className="h-[484px]">
          <AgentToolTimelineChatInput status={state.status} prompt={state.prompt} calls={state.calls} answer={state.answer} value={state.value} activityOpen={activityOpen} onActivityOpenChange={(open) => { interacted.current = true; setActivityOpen(open); }} onValueChange={(value) => {
            interacted.current = true;
            if (!session.manual && state.status === "idle") setPaused(true);
            setState((old) => ({ ...old, value }));
          }} onSubmit={send} onStop={() => { setPaused(true); setSession((old) => ({ ...old, manual: false })); setState((old) => ({ ...blank(), value: old.prompt })); }} />
        </div>
        <footer className="mt-3 flex items-center justify-between text-[9px] text-muted-foreground"><span>Demo data · No tools are actually run</span><button type="button" data-replay onClick={() => send(TASK)} className="inline-flex items-center gap-1.5 rounded px-1 py-1 outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"><RotateCcw className="size-3" aria-hidden="true" />Replay</button></footer>
      </div>
    </main>
  );
}

Dependencies

More by DHxWhy

View all
  • Sun Moon Dark Mode Theme Toggle

    Sun Moon Dark Mode Theme Toggle

    186
  • Live Notification Feed

    Live Notification Feed

    110
  • Country Flags Globe

    Country Flags Globe

    110
  • Airplane Window Gallery

    Airplane Window Gallery

    96
  • Domino Scroll Image Gallery

    Domino Scroll Image Gallery

    93
  • QR Code Scan Login Form with Password Fallback

    QR Code Scan Login Form with Password Fallback

    82
  • Constellation Image Gallery

    Constellation Image Gallery

    80
  • Ferris Wheel Image Gallery

    Ferris Wheel Image Gallery

    73

More in AI chats

View all
  • AI Actions

    AI Actions

    319
  • Color Picker

    Color Picker

    81
  • Agent Chat

    Agent Chat

    17
  • Prompt with Model Picker

    Prompt with Model Picker

    2
  • Message Thread

    Message Thread

    35
  • System Message

    System Message

    54
  • Suggestions

    Suggestions

    28
  • Message With Header And Delivery Status

    Message With Header And Delivery Status

    38