Agent Panel
Mention an agent in a conversation and open it beside the chat: what it is doing right now, its plan, and everything it has done in the thread.
launch-week
3 people, 3 agentsInstallation#
Usage#
The panel fetches nothing. Keep each agent's state where you keep the conversation, render mentions with splitAgentMentions, and give every agent a view. Pair it with PromptInputMentions so typing @ offers the agents in the composer.
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
On a narrow screen#
The split reads its own width, not the window's. Under 44rem the panel slides over the chat and the close button brings the thread back, with focus on the mention you tapped.
launch-week
3 people, 3 agentsAnatomy#
import {
AgentSplit,
AgentSplitMain,
AgentMention,
AgentPanel,
AgentPanelView,
AgentPanelHeader,
AgentPanelHeading,
AgentPanelTitle,
AgentPanelDescription,
AgentPanelClose,
AgentPanelBody,
AgentPanelNow,
AgentPanelRequest,
AgentPanelSection,
AgentPanelSectionTitle,
AgentPanelLog,
AgentPanelLogItem,
AgentPanelFooter,
} from "@/components/ui/agent-panel"
<AgentSplit>
<AgentSplitMain>
<AgentMention agent="scout" />
</AgentSplitMain>
<AgentPanel>
<AgentPanelView value="scout">
<AgentPanelHeader>
<AgentPanelHeading>
<AgentPanelTitle />
<AgentPanelDescription />
</AgentPanelHeading>
<AgentPanelClose />
</AgentPanelHeader>
<AgentPanelBody>
<AgentPanelSection>
<AgentPanelSectionTitle />
<AgentPanelRequest />
<AgentPanelNow />
</AgentPanelSection>
<AgentPanelSection>
<AgentPanelSectionTitle />
<AgentPanelLog>
<AgentPanelLogItem />
</AgentPanelLog>
</AgentPanelSection>
</AgentPanelBody>
<AgentPanelFooter />
</AgentPanelView>
</AgentPanel>
</AgentSplit>API reference#
AgentSplit
The split and the open agent every part reads. Two columns: the main view and the panel. The panel's column grows from nothing, so the chat narrows instead of being covered. Under 44rem of its own width the panel covers the main view and slides in from the right. Every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | null | - | The open agent, when you control it. null closes the panel. |
| defaultValue | string | null | null | The agent open on the first render. |
| onValueChange | (agent: string | null) => void | - | An agent was opened, switched or closed. |
| panelWidth | string | "24rem" | The panel's width when there is room for both columns, any CSS length. |
| Data attribute | Description |
|---|---|
| data-open | Present while an agent is open. |
AgentSplitMain
The main column, a flex column that can shrink. Drop the conversation inside.
AgentMention
A mention of an agent, a <button> that opens it in the panel, and closes it when it is already open. It returns focus here when the panel closes. Children are the name.
| Prop | Type | Default | Description |
|---|---|---|---|
| agent* | string | - | The agent's id, the value of its AgentPanelView. |
| variant | "chip" | "plain" | "chip" | chip sits inside message text, with the agent's mascot. plain is a bare name, for an author line. |
| mark | React.ReactNode | - | The agent's face before the name, sized to the line: pass its AgentMascot with the agent's status, so the face says how it is doing. Chip only. |
| Data attribute | Description |
|---|---|
| data-state="open" | "closed" | open while this agent is the one in the panel: the chip lifts onto the page. |
AgentPanel
The panel, an <aside> that stays mounted so it can animate. It goes inert while closed, takes focus when it opens and closes on Escape.
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | "Agent" | Names the region. |
| Data attribute | Description |
|---|---|
| data-state="open" | "closed" | On the panel and on its sheet. |
AgentPanelView
One agent's contents. Only the open agent's view renders. Switching agents with the panel open plays the new view in; closing keeps the last view on screen while it slides out.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | The agent this view belongs to. |
| Data attribute | Description |
|---|---|
| data-swap | Present when this view replaced another one while the panel was open. |
AgentPanelHeader
The top row with a hairline under it, 44px like ChannelChatHeader so both headers line up across the split: the agent's AgentMascot (small, contained), the heading and the close button.
AgentPanelHeading
One line in the header: the name, then the role and status.
AgentPanelTitle
The agent's name, an <h2> that truncates.
AgentPanelDescription
The muted text after the name, for the role and the status. Truncates before the name does.
AgentPanelClose
A ghost icon button that closes the panel. Takes every Button prop.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | "Close" | The accessible name. |
AgentPanelBody
The only scrolling region, a column of sections on whitespace. Lead it with the agent's properties (a DescriptionList split 50/50: who asked, when it started, what it can reach), then one section for the job that matters now, then its earlier work.
AgentPanelNow
What the agent is doing this second, as one flat row: the status dot, the line, and the elapsed time on the right in tabular figures while it works. Announced politely; a new line rises in when the text changes. Use it for the outcome too, with status="done".
| Prop | Type | Default | Description |
|---|---|---|---|
| status | "working" | "waiting" | "idle" | "done" | "failed" | "cancelled" | "working" | Sets the dot. |
| since | number | - | When the current task started, in epoch ms. Turns the timer on. |
| until | number | - | Freezes the timer at this time. |
| label | React.ReactNode | - | A muted word before the line, when the section title does not already say it. |
| activityKey | React.Key | - | Replays the line's entrance when it changes. Defaults to the text itself. |
AgentPanelRequest
What the agent was asked, quoted: the panel's one grey block. Put it first in the job section (Working on, Just finished) so the work always reads against the ask.
| Prop | Type | Default | Description |
|---|---|---|---|
| author | React.ReactNode | - | Who asked, in a small muted line ("You asked"). |
| avatar | React.ReactNode | - | A 16px avatar before the author. |
| onSelect | () => void | - | Makes the quote a button, to scroll the chat to the message it came from. |
AgentPanelSection
A titled group in the body. Drop a Plan, an AgentPanelLog, files or anything else inside.
AgentPanelSectionTitle
A section's small muted title.
| Prop | Type | Default | Description |
|---|---|---|---|
| count | number | - | A tabular count after the title. |
AgentPanelLog
What the agent has done, an <ol> that bleeds 8px so its rows' hover fill lines up with the text.
AgentPanelLogItem
One thing the agent did, or one job it handed back. Children are the title.
| Prop | Type | Default | Description |
|---|---|---|---|
| description | React.ReactNode | - | The outcome under the title. With it the row reads as a piece of work and drops the marker. |
| time | React.ReactNode | - | When, in tabular figures at the end of the row. |
| icon | React.ReactNode | - | Replaces the small marker. |
| onSelect | () => void | - | Makes the row a button, for instance to scroll to the message it produced. |
AgentPanelFooter
Optional actions pinned under the body, sharing the width. Most panels need none: keep a decision (Approve) inside the section it belongs to.
splitAgentMentions
splitAgentMentions(text, agents, trigger?) cuts a message into plain strings and { agent, text } mentions of the agents you pass ({ id, name }[]), longest name first, so you can render each mention as an AgentMention.
useAgentSplit
{ agent, open, toggle, close } for a trigger of your own, a roster row or a menu item. toggle(agent, element?) remembers the element to return focus to.
agentPanelVariants
The tv recipe behind every part.