Research progress
A deep research run from plan to report: four phases, the sources read, elapsed time, the page being read right now, and Cancel.
On this page
Plan, phases, sources read, the page being read right now, then the report.
Installation
Usage
import { ResearchProgress } from "@/components/ui/chatcn/research-progress"
<ResearchProgress
title="How long should a line of text be on screen?"
status="running"
phase="reading"
plan={plan}
sourcesRead={37}
sourcesFound={112}
current={{ kind: "read", text: "Readability: the optimal line length", url }}
startedAt={run.startedAt}
onCancel={cancel}
onOpenReport={openReport}
/>Research agents usually stream their state as a data part. Send one data-research part and replace its data on every update, then pass the fields straight through. The demo builds exactly that with the chatcn script() builder.
const part = message.parts.find((p) => p.type === "data-research")
return part ? <ResearchProgress title={question} {...part.data} onCancel={stop} /> : nullExamples
Every state
A cancelled run that has read enough can still offer a partial report. A failed run says what went wrong and how to recover.
Queued, complete, cancelled with a partial report, and failed.
States
| Status | Shows |
|---|---|
queued | An empty circle, when the run will start, and Cancel |
running | Spinner, elapsed time, phases, the live line, plan and Cancel |
complete | Check, duration, sources read, a summary and Open report |
cancelled | Stop icon, where it stopped, and Open partial report when onOpenReport is set |
failed | Red cross, Failed, the error and Retry research |
Props
| Prop | Type | Notes |
|---|---|---|
title | ReactNode | The question or topic |
status | ResearchStatus | queued, running, complete, cancelled or failed |
phase | ResearchPhase | planning, searching, reading or writing. Earlier phases read as done |
phaseLabels | Partial<Record<ResearchPhase, string>> | Rename phases |
plan | ResearchPlanStep[] | { id, title, status? } with pending, active, done or skipped |
sourcesRead, sourcesFound | number | Rendered as is on every update. No count-up |
current | ResearchCurrent | { kind, text, url? } for the live line |
activity | ResearchActivity[] | Searches and reads for the collapsed log |
startedAt, durationMs | number | Optional. Measured when omitted |
error, onRetry | For failed | |
summary | ReactNode | One line on complete |
onCancel | () => void | Shows Cancel while queued or running |
onOpenReport, reportLabel | The button on complete and cancelled |
Keyboard
| Key | Action |
|---|---|
Tab | Cancel, the activity log toggle and the report button |
Enter, Space | Open or close the activity log |
Accessibility
- Phases and plan steps are ordered lists. The current one carries
aria-current="step", and done items say done to screen readers. - A polite live region announces each phase change and the result once. The source count is never announced per update.
- The live line's shimmer is static under reduced motion.
Motion
- The page being read crossfades in from a 2 px blur over 150 ms: one line changing, not a list scrolling past. Opacity only under reduced motion.
- Status and plan icons swap with the 150 ms blur crossfade. Counters and the timer change in place, once per update and once per second.
- The activity log opens with a 200 ms height and opacity transition and follows new entries only while you are at its bottom.