= {
+ args: {},
+};
diff --git a/docs/storybook/stories/agents-ui/Introduction.mdx b/docs/storybook/stories/agents-ui/Introduction.mdx
new file mode 100644
index 000000000..e87a2f942
--- /dev/null
+++ b/docs/storybook/stories/agents-ui/Introduction.mdx
@@ -0,0 +1,45 @@
+import { Meta } from '@storybook/addon-docs/blocks';
+
+
+
+# Agents UI
+
+Agents UI is a set of components for building voice-first agents using LiveKit.
+
+## Setup
+
+Configure our registry in your project by adding the following to your projects `components.json` file:
+
+```json | components.json
+{
+ "registries": {
+ "@agents-ui": "https://ui.livekit.io/r/{name}.json"
+ }
+}
+```
+
+## Installation
+
+```bash
+pnpm dlx shadcn@latest add @agents-ui/{componentName}
+```
+
+## Implementation Example
+
+```tsx
+import { useSession } from '@livekit/components-react';
+import { TokenSource } from 'livekit-client';
+import { AgentSessionProvider } from '@agents-ui/agent-session-provider';
+import { AgentControlBar } from '@agents-ui/agent-control-bar';
+
+export default function Example() {
+ // using a sandbox token server for prototyping
+ const session = useSession(TokenSource.sandboxTokenServer('token-XXXXXX'));
+
+ return (
+
+
+
+ );
+}
+```
diff --git a/docs/storybook/stories/agents-ui/StartAudioButton.stories.tsx b/docs/storybook/stories/agents-ui/StartAudioButton.stories.tsx
new file mode 100644
index 000000000..252d7b179
--- /dev/null
+++ b/docs/storybook/stories/agents-ui/StartAudioButton.stories.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+import { StoryObj } from '@storybook/react-vite';
+import { AgentSessionProvider } from '../../.storybook/lk-decorators/AgentSessionProvider';
+import { StartAudioButton, type StartAudioButtonProps } from '@agents-ui';
+
+export default {
+ component: StartAudioButton,
+ decorators: [AgentSessionProvider],
+ render: (args: StartAudioButtonProps) => {
+ return (
+ <>
+ A button will be rendered below if audio playback is blocked.
+
+ >
+ );
+ },
+ args: {
+ label: 'Click to allow audio playback',
+ },
+ argTypes: {
+ label: { control: { type: 'text' } },
+ onClick: { action: 'onClick' },
+ className: { control: { type: 'text' } },
+ },
+ parameters: {
+ layout: 'centered',
+ actions: {
+ handles: [],
+ },
+ },
+};
+
+export const Default: StoryObj = {
+ args: {},
+};
diff --git a/packages/shadcn/.env.example b/packages/shadcn/.env.example
new file mode 100644
index 000000000..8bbb8ae4e
--- /dev/null
+++ b/packages/shadcn/.env.example
@@ -0,0 +1 @@
+DEST_PATH=/path/to/dest/folder
\ No newline at end of file
diff --git a/packages/shadcn/.gitignore b/packages/shadcn/.gitignore
index 1734597ac..efb48f5e5 100644
--- a/packages/shadcn/.gitignore
+++ b/packages/shadcn/.gitignore
@@ -4,3 +4,5 @@ node_modules
.cache
dist
temp
+
+!.env.example
\ No newline at end of file
diff --git a/packages/shadcn/components/agents-ui/agent-audio-visualizer-bar.tsx b/packages/shadcn/components/agents-ui/agent-audio-visualizer-bar.tsx
index a4b0f92e1..e9687b4f6 100644
--- a/packages/shadcn/components/agents-ui/agent-audio-visualizer-bar.tsx
+++ b/packages/shadcn/components/agents-ui/agent-audio-visualizer-bar.tsx
@@ -73,8 +73,8 @@ export interface AgentAudioVisualizerBarProps {
}
export function AgentAudioVisualizerBar({
- size,
- state,
+ size = 'md',
+ state = 'connecting',
barCount,
audioTrack,
className,
diff --git a/packages/shadcn/components/agents-ui/agent-audio-visualizer-grid.tsx b/packages/shadcn/components/agents-ui/agent-audio-visualizer-grid.tsx
index c76e1da4f..1a9c61531 100644
--- a/packages/shadcn/components/agents-ui/agent-audio-visualizer-grid.tsx
+++ b/packages/shadcn/components/agents-ui/agent-audio-visualizer-grid.tsx
@@ -159,7 +159,7 @@ const GridCell = memo(function GridCell({
});
export type AgentAudioVisualizerGridProps = GridOptions & {
- state: AgentState;
+ state?: AgentState;
audioTrack?: LocalAudioTrack | RemoteAudioTrack | TrackReferenceOrPlaceholder;
className?: string;
children?: ReactNode;
@@ -167,7 +167,7 @@ export type AgentAudioVisualizerGridProps = GridOptions & {
export function AgentAudioVisualizerGrid({
size = 'md',
- state,
+ state = 'connecting',
radius,
rowCount: _rowCount = 5,
columnCount: _columnCount = 5,
diff --git a/packages/shadcn/components/agents-ui/agent-audio-visualizer-radial.tsx b/packages/shadcn/components/agents-ui/agent-audio-visualizer-radial.tsx
index 637c1e762..f0387e485 100644
--- a/packages/shadcn/components/agents-ui/agent-audio-visualizer-radial.tsx
+++ b/packages/shadcn/components/agents-ui/agent-audio-visualizer-radial.tsx
@@ -44,8 +44,8 @@ export interface AgentAudioVisualizerRadialProps {
}
export function AgentAudioVisualizerRadial({
- size,
- state,
+ size = 'md',
+ state = 'connecting',
radius,
barCount,
audioTrack,
diff --git a/packages/shadcn/components/agents-ui/agent-chat-indicator.tsx b/packages/shadcn/components/agents-ui/agent-chat-indicator.tsx
new file mode 100644
index 000000000..114e3d706
--- /dev/null
+++ b/packages/shadcn/components/agents-ui/agent-chat-indicator.tsx
@@ -0,0 +1,57 @@
+import { motion } from 'motion/react';
+import { cva, VariantProps } from 'class-variance-authority';
+import { cn } from '@/lib/utils';
+
+const motionAnimationProps = {
+ variants: {
+ hidden: {
+ opacity: 0,
+ scale: 0.1,
+ transition: {
+ duration: 0.1,
+ ease: 'linear',
+ },
+ },
+ visible: {
+ opacity: [0.5, 1],
+ scale: [1, 1.2],
+ transition: {
+ type: 'spring',
+ bounce: 0,
+ duration: 0.5,
+ repeat: Infinity,
+ repeatType: 'mirror' as const,
+ },
+ },
+ },
+ initial: 'hidden',
+ animate: 'visible',
+ exit: 'hidden',
+};
+
+const agentChatIndicatorVariants = cva('bg-muted-foreground inline-block size-2.5 rounded-full', {
+ variants: {
+ size: {
+ sm: 'size-2.5',
+ md: 'size-4',
+ lg: 'size-6',
+ },
+ },
+ defaultVariants: {
+ size: 'md',
+ },
+});
+
+export interface AgentChatIndicatorProps extends VariantProps {
+ className?: string;
+}
+
+export function AgentChatIndicator({ size, className }: AgentChatIndicatorProps) {
+ return (
+
+ );
+}
diff --git a/packages/shadcn/components/agents-ui/agent-chat-transcript.tsx b/packages/shadcn/components/agents-ui/agent-chat-transcript.tsx
new file mode 100644
index 000000000..2b45cf62c
--- /dev/null
+++ b/packages/shadcn/components/agents-ui/agent-chat-transcript.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import { type AgentState, type ReceivedMessage } from '@livekit/components-react';
+import {
+ Conversation,
+ ConversationContent,
+ ConversationScrollButton,
+} from '@/components/ai-elements/conversation';
+import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message';
+import { AgentChatIndicator } from '@/components/agents-ui/agent-chat-indicator';
+import { AnimatePresence } from 'motion/react';
+
+export interface AgentChatTranscriptProps {
+ agentState?: AgentState;
+ messages?: ReceivedMessage[];
+ className?: string;
+}
+
+export function AgentChatTranscript({
+ agentState,
+ messages = [],
+ className,
+}: AgentChatTranscriptProps) {
+ return (
+
+
+ {messages.map((receivedMessage) => {
+ const { id, timestamp, from, message } = receivedMessage;
+ const locale = navigator?.language ?? 'en-US';
+ const messageOrigin = from?.isLocal ? 'user' : 'assistant';
+ const time = new Date(timestamp);
+ const title = time.toLocaleTimeString(locale, { timeStyle: 'full' });
+
+ return (
+
+
+ {message}
+
+
+ );
+ })}
+
+ {agentState === 'thinking' && }
+
+
+
+
+ );
+}
diff --git a/packages/shadcn/components/agents-ui/agent-session-provider.tsx b/packages/shadcn/components/agents-ui/agent-session-provider.tsx
new file mode 100644
index 000000000..f064dfe08
--- /dev/null
+++ b/packages/shadcn/components/agents-ui/agent-session-provider.tsx
@@ -0,0 +1,21 @@
+import {
+ SessionProvider,
+ RoomAudioRenderer,
+ type SessionProviderProps,
+ type RoomAudioRendererProps,
+} from '@livekit/components-react';
+
+export type AgentSessionProviderProps = SessionProviderProps & RoomAudioRendererProps;
+
+export function AgentSessionProvider({
+ session,
+ children,
+ ...roomAudioRendererProps
+}: AgentSessionProviderProps) {
+ return (
+
+ {children}
+
+
+ );
+}
diff --git a/packages/shadcn/components/agents-ui/start-audio-button.tsx b/packages/shadcn/components/agents-ui/start-audio-button.tsx
new file mode 100644
index 000000000..4737a57d7
--- /dev/null
+++ b/packages/shadcn/components/agents-ui/start-audio-button.tsx
@@ -0,0 +1,15 @@
+import { useEnsureRoom, useStartAudio } from '@livekit/components-react';
+import { Button } from '@/components/ui/button';
+import { Room } from 'livekit-client';
+
+export interface StartAudioButtonProps extends React.ButtonHTMLAttributes {
+ room?: Room;
+ label: string;
+}
+
+export function StartAudioButton({ label, room, ...props }: StartAudioButtonProps) {
+ const roomEnsured = useEnsureRoom(room);
+ const { mergedProps } = useStartAudio({ room: roomEnsured, props });
+
+ return ;
+}
diff --git a/packages/shadcn/components/session-provider.tsx b/packages/shadcn/components/session-provider.tsx
new file mode 100644
index 000000000..41d52eb25
--- /dev/null
+++ b/packages/shadcn/components/session-provider.tsx
@@ -0,0 +1,15 @@
+'use client';
+import type { ReactNode } from 'react';
+import { TokenSource } from 'livekit-client';
+import { useSession, SessionProvider as LiveKitSessionProvider } from '@livekit/components-react';
+
+type SessionProviderProps = {
+ children: ReactNode;
+};
+
+export function SessionProvider({ children }: SessionProviderProps) {
+ const tokenSource = TokenSource.endpoint(process.env.NEXT_PUBLIC_LK_TOKEN_ENDPOINT);
+ const session = useSession(tokenSource);
+
+ return {children};
+}
diff --git a/packages/shadcn/index.ts b/packages/shadcn/index.ts
index 9578af194..ae91f4c81 100644
--- a/packages/shadcn/index.ts
+++ b/packages/shadcn/index.ts
@@ -1,7 +1,11 @@
-export * from './components/agents-ui/agent-audio-visualizer-bar';
+export * from './components/agents-ui/agent-session-provider';
+export * from './components/agents-ui/start-audio-button';
+export * from './components/agents-ui/agent-disconnect-button';
export * from './components/agents-ui/agent-track-toggle';
export * from './components/agents-ui/agent-track-control';
-export * from './components/agents-ui/agent-disconnect-button';
export * from './components/agents-ui/agent-control-bar';
-export * from './components/agents-ui/agent-audio-visualizer-radial';
+export * from './components/agents-ui/agent-chat-indicator';
+export * from './components/agents-ui/agent-chat-transcript';
+export * from './components/agents-ui/agent-audio-visualizer-bar';
export * from './components/agents-ui/agent-audio-visualizer-grid';
+export * from './components/agents-ui/agent-audio-visualizer-radial';
diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json
index 91da571a4..215fc9e1c 100644
--- a/packages/shadcn/package.json
+++ b/packages/shadcn/package.json
@@ -6,8 +6,9 @@
"type": "module",
"main": "index.ts",
"scripts": {
- "registry:build": "shadcn build --output ./dist",
- "registry:serve": "python3 -m http.server 3210 -d dist"
+ "registry:build": "rm -rf dist && shadcn build --output ./dist",
+ "registry:serve": "python3 -m http.server 3210 -d dist",
+ "registry:update": "pnpm registry:build && node --experimental-strip-types --env-file=.env.local ./scripts/update.ts"
},
"keywords": [],
"author": "",
diff --git a/packages/shadcn/registry.json b/packages/shadcn/registry.json
index 9253c311c..85e3be232 100644
--- a/packages/shadcn/registry.json
+++ b/packages/shadcn/registry.json
@@ -157,6 +157,65 @@
"class-variance-authority"
],
"registryDependencies": ["utils"]
+ },
+ {
+ "name": "agent-session-provider",
+ "type": "registry:component",
+ "title": "Agent Session Provider",
+ "description": "A component for providing an agent's session context and ensuring remote participants’ audio tracks (microphones and screen share) are audible.",
+ "files": [
+ {
+ "path": "components/agents-ui/agent-session-provider.tsx",
+ "type": "registry:component"
+ }
+ ],
+ "dependencies": ["@livekit/components-react@^2.0.0"]
+ },
+ {
+ "name": "start-audio-button",
+ "type": "registry:component",
+ "title": "Start Audio Button",
+ "description": "A button for starting the agent session's audio track when the browser blocks audio playback.",
+ "files": [
+ {
+ "path": "components/agents-ui/start-audio-button.tsx",
+ "type": "registry:component"
+ }
+ ],
+ "dependencies": ["livekit-client@^2.0.0", "@livekit/components-react@^2.0.0"],
+ "registryDependencies": ["button"]
+ },
+ {
+ "name": "agent-chat-indicator",
+ "type": "registry:component",
+ "title": "Agent Chat Indicator",
+ "description": "A component for indicating the agent's chat status with a blinking dot. Useful for indicating the agent is thinking before generating a response.",
+ "files": [
+ {
+ "path": "components/agents-ui/agent-chat-indicator.tsx",
+ "type": "registry:component"
+ }
+ ],
+ "dependencies": ["motion", "class-variance-authority"],
+ "registryDependencies": ["utils"]
+ },
+ {
+ "name": "agent-chat-transcript",
+ "type": "registry:component",
+ "title": "Agent Chat Transcript",
+ "description": "A component for displaying the agent session's chat transcript.",
+ "files": [
+ {
+ "path": "components/agents-ui/agent-chat-transcript.tsx",
+ "type": "registry:component"
+ }
+ ],
+ "dependencies": ["@livekit/components-react@^2.0.0", "motion"],
+ "registryDependencies": [
+ "@ai-elements/message",
+ "@ai-elements/conversation",
+ "@agents-ui/agent-chat-indicator"
+ ]
}
]
}
diff --git a/packages/shadcn/scripts/update.ts b/packages/shadcn/scripts/update.ts
new file mode 100644
index 000000000..cf5ab5661
--- /dev/null
+++ b/packages/shadcn/scripts/update.ts
@@ -0,0 +1,16 @@
+import { execSync } from 'node:child_process';
+
+// copy .env.example to .env.local and edit DEST_PATH
+if (!process.env.DEST_PATH) {
+ throw new Error('DEST_PATH is not set');
+}
+
+console.log('--------------------------------');
+console.log(`Cleaning ${process.env.DEST_PATH}`);
+execSync(`rm -rf ${process.env.DEST_PATH}/*`);
+console.log('--------------------------------');
+console.log(`Copying dist to ${process.env.DEST_PATH}`);
+execSync(`cp -r dist/* ${process.env.DEST_PATH}`);
+console.log('--------------------------------');
+
+console.log('Done');