Skip to content

feat(agents-ui): add AgentComposer, a text input with a microphone - #1500

Draft
jeffdaley wants to merge 23 commits into
mainfrom
jeffdaley/agents-ui-composer
Draft

jeffdaley wants to merge 23 commits into
mainfrom
jeffdaley/agents-ui-composer

Conversation

@jeffdaley

@jeffdaley jeffdaley commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Ticket

https://linear.app/livekit/issue/DE-206/agents-ui-add-agentcomposer-text-input-with-microphone

Context / reason for change

Agents UI has no standalone way to type to an agent. The chat input only exists inside AgentControlBar, can't be placed or styled on its own, and loses your text when a message fails.

Agents are increasingly hybrid: you type some turns and speak others. The composer is the text input for that, with the microphone alongside it, so an app can offer both without building its own input.

Summary of changes

  • AgentComposer, built from parts (Field, Input, Actions, Microphone, Submit, CharacterCount) plus a headless useAgentComposer hook. It sends through the session's chat by default, or through onSend.
  • Clears as soon as you send, so you can type the next message right away. A message that fails goes back in the input, unless something new has been typed, and onError reports the error so the app can show it, such as in a toast. There's no failed state or retry in the transcript; message status waits for the client SDK's upcoming useThreadMessages.
  • The microphone is a toggle, not press-and-hold. Turning it on lets the agent hear you, the same as the control bar. Spoken turns don't wait for Send. While it's on, it shows the level and the placeholder reads "Listening…". It shows a spinner while starting and says when access is blocked or there's no microphone.
    • Press-and-hold was ruled out because it's hard to use with assistive tech and on touch screens.
  • maxLength counts instead of cutting you off. Typing past the limit is allowed. A "12/200" count sits in a row below the input, turns red when over, and sending waits until the message fits. Emoji and accented letters count once.
  • Stop is UI only. With responding and onStop, Send becomes Stop while the input is empty. The SDK has no client interrupt API yet, so the app decides what stopping does.
  • Enter sends and Shift+Enter adds a line. On touch screens, Return adds a line. IME composition never sends.
  • draftKey keeps unsent text in sessionStorage across reloads. Drafts load after mount, so server-rendered HTML matches.
  • Without onSend, the microphone hides when the participant can't publish audio.
  • onSubmit can cancel a send with preventDefault, on Enter and on the button alike.
  • labels on the composer and the transcript, for rewording and translation.
  • icons on the composer replaces the send, stop, and microphone icons, including each microphone state, so they can be swapped without rebuilding the layout.
  • AgentControlBar uses the composer for its chat input, without the microphone, since the bar already has one.
  • Your own messages render as plain text in the transcript, so line breaks stay as typed. Agent messages still render as Markdown.

Screenshots / demos

To add: a failed send putting the message back; the count over the limit; the microphone listening, pending, and blocked; Stop; light and dark.

How to verify

Run Storybook (pnpm --filter @livekit/component-docs-storybook dev) and open Agents UI / AgentComposer:

  • Default: type and send. The input clears right away, and the message appears once it's sent.
  • SendFails: send. The message goes back in the input, and onError appears in the Actions panel.
  • MaxLength: type past 200 characters. The count turns red and Send disables.
  • StopReply: send, then press Stop while the reply is pending.
  • MicrophoneBlocked: the mic shows as blocked. Pressing it clears the error and retries.
  • Draft: type, reload the page, and the text is still there.
  • LiveAgent: needs VITE_PUBLIC_LK_SANDBOX_TOKEN_SERVER_ID in docs/storybook/.env. Type and speak to a real agent.

On a phone, Return should add a new line rather than send.

Submitter checklist

  • I ran the change locally and inspected the affected UI/flow myself, if applicable.
  • I added or updated tests covering this change (unit, play function, or E2E), or explained why none are needed.
  • I attached screenshots or a demo for user-facing changes (ideally before + after), if applicable.
  • I self-reviewed the diff and removed debug code, dead code, and stray comments.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
components-js-storybook-5kld Ready Ready Preview Nov 9, 2026 3:25pm UTC

Request Review

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@changeset-bot

changeset-bot Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8aa6171

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@livekit/agents-ui Minor
@livekit/component-docs-storybook Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

jeffdaley and others added 21 commits November 9, 2026 09:53
A composable text input for sending messages to an agent, with a headless
useAgentComposer hook. AgentControlBar now uses it for its chat input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The composer clears as soon as you send and no longer waits for the send
to finish. Sending and failed states now live on each message, and the
transcript offers a retry for failed messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ength

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also shows a listen toggle and a plus attach button in the composer story.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t for maxLength

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changes the placeholder, adds data-listening, and shows the microphone's level in its button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing replies in the composer

Adds labels to the composer and transcript, a connecting placeholder, drafts, and a screen
reader announcement for messages that can't send. Removes the unused ghost variant from
AgentTrackToggle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Calling `preventDefault` in `onSubmit` cancels the send, as it already did for Enter.
- The microphone button hides when the participant can't publish audio.
- `onMicrophoneError` always calls the latest callback.
- Drafts load after mount, so server-rendered HTML matches, and a new `draftKey` loads its own draft.
- Enter sends in browsers without `requestSubmit`.
- The stop button is disabled while the composer is.
- A label passed as `undefined` falls back to its default.
- The transcript announces a failure only when a message newly fails, not when retrying one leaves an older failure behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`maxLength` and the character count count graphemes, so an emoji or accented letter counts once
instead of as two or more UTF-16 code units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nput

AgentControlBar clears its input as soon as you send, so a failed send lost the text unless a
transcript showed the failure. It now puts the message back if nothing new has been typed.
`restoreFailedMessages={false}` turns this off for transcripts that already offer a retry, and
AgentSessionView_01 uses it so there's one way to resend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and transcript

The composer no longer depends on #1497. A message that fails to send goes back in the composer,
unless something new has been typed, and onError reports the error. The transcript no longer marks
failed messages or offers a retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jeffdaley
jeffdaley force-pushed the jeffdaley/agents-ui-composer branch from 7d7e3b7 to 4154c42 Compare November 9, 2026 15:18
@jeffdaley
jeffdaley changed the base branch from jeffdaley/chat-message-status to main November 9, 2026 15:18
AgentComposer takes icons, like labels, to replace the send, stop, and microphone icons, including
each microphone state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
LiveKitRoom only 7.88 KB (0%)
LiveKitRoom with VideoConference 32.63 KB (0%)
All exports 44.44 KB (0%)

This branch was successfully deployed

1 active deployment
Preview — 8aa61713 Deployed Nov 9, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants