{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"search-tool-call","type":"registry:ui","title":"Search Tool Call","description":"A web search the model ran — the query it chose, how many results came back, and previews of the sources it read.","author":"Scrim UI (https://scrimui.dev)","categories":["tool-calls"],"docs":"https://scrimui.dev/components/search-tool-call","dependencies":[],"registryDependencies":[],"files":[{"path":"src/showcase/search-tool-call/search-tool-call.tsx","content":"\"use client\";\n\nimport * as React from \"react\";\n\n/* ------------------------------------------------------------------ */\n/* Types                                                               */\n/* ------------------------------------------------------------------ */\n\nexport type SearchResult = {\n  title: string;\n  url: string;\n  snippet?: string;\n};\n\nexport type SearchToolCallProps = {\n  query: string;\n  status?: \"searching\" | \"done\" | \"error\";\n  results?: SearchResult[];\n  elapsed?: string;\n  onStop?: () => void;\n  onRetry?: () => void;\n  className?: string;\n};\n\n/* ------------------------------------------------------------------ */\n/* Icons                                                               */\n/* ------------------------------------------------------------------ */\n\nfunction GlobeIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"15\" height=\"15\" {...props}>\n      <circle cx=\"12\" cy=\"12\" r=\"10\" />\n      <path d=\"M2 12h20M12 2a15.3 15.3 0 0 1 0 20 15.3 15.3 0 0 1 0-20Z\" />\n    </svg>\n  );\n}\n\nfunction ChevronIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"14\" height=\"14\" {...props}>\n      <path d=\"m6 9 6 6 6-6\" />\n    </svg>\n  );\n}\n\nfunction StopIcon(props: React.SVGProps<SVGSVGElement>) {\n  return (\n    <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"10\" height=\"10\" {...props}>\n      <rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" />\n    </svg>\n  );\n}\n\nfunction domainFromUrl(url: string) {\n  try {\n    return new URL(url).hostname.replace(/^www\\./, \"\");\n  } catch {\n    return url;\n  }\n}\n\n/* ------------------------------------------------------------------ */\n/* SearchToolCall                                                      */\n/* ------------------------------------------------------------------ */\n\nexport function SearchToolCall({\n  query,\n  status = \"searching\",\n  results = [],\n  elapsed,\n  onStop,\n  onRetry,\n  className = \"\",\n}: SearchToolCallProps) {\n  const [open, setOpen] = React.useState(status === \"done\");\n\n  return (\n    <div className={`overflow-hidden rounded-xl border border-zinc-200 bg-white transition-colors dark:border-zinc-800 dark:bg-zinc-900 ${className}`}>\n      {/* Header */}\n      <button\n        type=\"button\"\n        onClick={() => setOpen((v) => !v)}\n        aria-expanded={open}\n        className=\"flex w-full items-center gap-2.5 px-3.5 py-2.5 text-left\"\n      >\n        <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-blue-100 text-blue-600 dark:bg-blue-900/40 dark:text-blue-400\">\n          <GlobeIcon />\n        </span>\n        <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-zinc-900 dark:text-zinc-100\">\n          {status === \"searching\" ? \"Searching the web\" : \"Search the web\"}\n        </span>\n        <span className=\"hidden shrink-0 truncate text-xs text-zinc-500 dark:text-zinc-400 sm:block\">\n          {status === \"searching\" ? `\"${query}\"` : `${results.length} results`}\n        </span>\n        {elapsed && <span className=\"shrink-0 text-xs tabular-nums text-zinc-500 dark:text-zinc-400\">{elapsed}</span>}\n        {status === \"searching\" ? (\n          <span className=\"inline-flex shrink-0 items-center gap-1.5 rounded-full bg-blue-100 px-2 py-0.5 text-[11px] text-blue-700 dark:bg-blue-900/40 dark:text-blue-400\">\n            <span className=\"h-2.5 w-2.5 animate-spin rounded-full border-[1.5px] border-current border-t-transparent\" />\n            Searching\n          </span>\n        ) : status === \"error\" ? (\n          <span className=\"shrink-0 rounded-full bg-red-100 px-2 py-0.5 text-[11px] text-red-700 dark:bg-red-900/40 dark:text-red-400\">\n            Failed\n          </span>\n        ) : (\n          <span className=\"shrink-0 rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-400\">\n            {results.length} sources\n          </span>\n        )}\n        <span className={`shrink-0 text-zinc-400 transition-transform ${open ? \"rotate-180\" : \"\"}`}>\n          <ChevronIcon />\n        </span>\n      </button>\n\n      {/* Body */}\n      {open && (\n        <div className=\"border-t border-zinc-100 px-3.5 py-3 dark:border-zinc-800\">\n          {status === \"searching\" && (\n            <div className=\"flex items-center justify-between\">\n              <p className=\"text-[13px] text-zinc-500 dark:text-zinc-400\">\n                Querying <span className=\"font-medium text-zinc-700 dark:text-zinc-200\">“{query}”</span>\n              </p>\n              {onStop && (\n                <button\n                  type=\"button\"\n                  onClick={onStop}\n                  className=\"inline-flex h-7 items-center gap-1.5 rounded-lg border border-zinc-200 px-2.5 text-xs text-zinc-500 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800\"\n                >\n                  <StopIcon />\n                  Stop\n                </button>\n              )}\n            </div>\n          )}\n\n          {status === \"error\" && (\n            <div className=\"flex items-center justify-between\">\n              <p className=\"text-[13px] text-red-600 dark:text-red-400\">\n                Search failed — check your network connection.\n              </p>\n              {onRetry && (\n                <button\n                  type=\"button\"\n                  onClick={onRetry}\n                  className=\"inline-flex h-7 items-center gap-1.5 rounded-lg border border-zinc-200 px-2.5 text-xs text-zinc-500 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800\"\n                >\n                  <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" width=\"12\" height=\"12\">\n                    <path d=\"M21 12a9 9 0 1 1-2.64-6.36L21 8\" />\n                    <path d=\"M21 3v5h-5\" />\n                  </svg>\n                  Retry\n                </button>\n              )}\n            </div>\n          )}\n\n          {status === \"done\" && (\n            <ul className=\"space-y-2.5\">\n              {results.map((r, i) => (\n                <li key={i}>\n                  <a\n                    href={r.url}\n                    target=\"_blank\"\n                    rel=\"noreferrer noopener\"\n                    className=\"group block\"\n                  >\n                    <p className=\"truncate text-sm font-medium text-zinc-800 group-hover:underline dark:text-zinc-100\">\n                      {r.title}\n                    </p>\n                    <p className=\"text-xs text-zinc-500 dark:text-zinc-400\">{domainFromUrl(r.url)}</p>\n                    {r.snippet && (\n                      <p className=\"mt-0.5 line-clamp-1 text-[13px] text-zinc-500 dark:text-zinc-400\">\n                        {r.snippet}\n                      </p>\n                    )}\n                  </a>\n                </li>\n              ))}\n            </ul>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n","type":"registry:ui","target":"components/ui/search-tool-call.tsx"}]}