Why did the 14 March payout land a day late?
KQ-1291 · ANSWER/PANEL
Answer Panel
An AI answer you can read while it is written: thoughts arrive one at a time on snap, then the answer streams token by token behind a caret, its numbered citation pills scaling in on snap where each claim is made and lifting their source card on snap when hovered or focused. When the text is done the Thinking block collapses on glide into a one-line summary, the actions and follow-ups rise in a cascade, a pressed thumb lands on recoil, and Regenerate rewinds the text on the move ease before writing again. Tab reaches every pill, thumb and follow-up; focusing a pill opens its card, Enter opens the source and Escape closes the card.
thinking · reading the batch log
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/answer-panel
Props
- question
- string
- default defaultQuestion
- What was asked; names the panel.
- answer
- string
- default defaultAnswer
- The answer. Blank lines split paragraphs, "- " starts a bullet, and [n] cites sources[n - 1].
- sources
- AnswerSource[]
- default defaultSources
- The sources the answer can cite, in citation order: title, site, snippet, meta.
- thoughts
- AnswerThought[]
- default defaultThoughts
- The steps taken before writing, each with its duration in ms.
- suggestions
- string[]
- default defaultSuggestions
- Follow-up questions offered once the answer is done.
- model
- string
- default "Fernworks Model 3"
- The model that wrote it, printed in the action row.
- status / defaultStatus
- "thinking" | "streaming" | "done" | "error"
- default — / "thinking"
- The lifecycle. Controlled, the panel asks for the next status and waits for the host.
- onStatusChange
- (status: AnswerStatus) => void
- default —
- Fires when the panel moves on: thinking finished, text finished, or Regenerate pressed.
- speed
- number
- default 32
- Tokens per second the answer is written at; thoughts hold longer when it is slow.
- citations
- "pill" | "number" | "none"
- default "pill"
- Citation markers as numbered pills, bare superscript numbers, or not at all.
- followups
- "list" | "chips" | "none"
- default "list"
- Follow-ups as full-width rows, wrapping chips, or not at all.
- feedback / defaultFeedback
- "up" | "down" | null
- default — / null
- The visitor's rating of the answer.
- onFeedbackChange
- (feedback: AnswerFeedback) => void
- default —
- Fires from the thumb that changed it, and with null when Regenerate clears it.
- onFeedbackReason
- (reason: string) => void
- default —
- The reason picked after Not helpful.
- reasons
- string[]
- default ["Inaccurate", "Out of date", "Missed the question", "Too long"]
- Reasons offered after Not helpful.
- onRegenerate
- () => void
- default —
- Regenerate or Retry was pressed. Swap `answer` here; the old text rewinds first.
- onFollowup
- (text: string) => void
- default —
- A follow-up was chosen.
- onSourceOpen
- (source: AnswerSource) => void
- default —
- A citation or a listed source was opened.
- onCopy
- (text: string) => void
- default —
- Copy was pressed, with the plain text sent to the clipboard (markers out, sources listed after).
- errorMessage
- string
- default "The answer stopped partway."
- What the error line says.
- sound
- boolean
- default false
- Play the thumbs, the copy, the disclosure and the rewind. The stream itself is silent.
- disabled
- boolean
- default false
- Disables the actions, feedback and follow-ups; the answer still reads.
- className
- string
- default —
- Give the panel a height (h-[32rem]) and its body scrolls inside it.
| Prop | Type | Default | Description |
|---|---|---|---|
| question | string | defaultQuestion | What was asked; names the panel. |
| answer | string | defaultAnswer | The answer. Blank lines split paragraphs, "- " starts a bullet, and [n] cites sources[n - 1]. |
| sources | AnswerSource[] | defaultSources | The sources the answer can cite, in citation order: title, site, snippet, meta. |
| thoughts | AnswerThought[] | defaultThoughts | The steps taken before writing, each with its duration in ms. |
| suggestions | string[] | defaultSuggestions | Follow-up questions offered once the answer is done. |
| model | string | "Fernworks Model 3" | The model that wrote it, printed in the action row. |
| status / defaultStatus | "thinking" | "streaming" | "done" | "error" | — / "thinking" | The lifecycle. Controlled, the panel asks for the next status and waits for the host. |
| onStatusChange | (status: AnswerStatus) => void | — | Fires when the panel moves on: thinking finished, text finished, or Regenerate pressed. |
| speed | number | 32 | Tokens per second the answer is written at; thoughts hold longer when it is slow. |
| citations | "pill" | "number" | "none" | "pill" | Citation markers as numbered pills, bare superscript numbers, or not at all. |
| followups | "list" | "chips" | "none" | "list" | Follow-ups as full-width rows, wrapping chips, or not at all. |
| feedback / defaultFeedback | "up" | "down" | null | — / null | The visitor's rating of the answer. |
| onFeedbackChange | (feedback: AnswerFeedback) => void | — | Fires from the thumb that changed it, and with null when Regenerate clears it. |
| onFeedbackReason | (reason: string) => void | — | The reason picked after Not helpful. |
| reasons | string[] | ["Inaccurate", "Out of date", "Missed the question", "Too long"] | Reasons offered after Not helpful. |
| onRegenerate | () => void | — | Regenerate or Retry was pressed. Swap `answer` here; the old text rewinds first. |
| onFollowup | (text: string) => void | — | A follow-up was chosen. |
| onSourceOpen | (source: AnswerSource) => void | — | A citation or a listed source was opened. |
| onCopy | (text: string) => void | — | Copy was pressed, with the plain text sent to the clipboard (markers out, sources listed after). |
| errorMessage | string | "The answer stopped partway." | What the error line says. |
| sound | boolean | false | Play the thumbs, the copy, the disclosure and the rewind. The stream itself is silent. |
| disabled | boolean | false | Disables the actions, feedback and follow-ups; the answer still reads. |
| className | string | — | Give the panel a height (h-[32rem]) and its body scrolls inside it. |