Skip to content

Commit 5a2abe6

Browse files
feat: add chat-indicator component
1 parent 319cd40 commit 5a2abe6

File tree

3 files changed

+89
-25
lines changed

3 files changed

+89
-25
lines changed

components/app/chat-transcript.tsx

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client';
22

33
import { AnimatePresence, type HTMLMotionProps, motion } from 'motion/react';
4-
import { type ReceivedMessage } from '@livekit/components-react';
4+
import { type ReceivedMessage, useVoiceAssistant } from '@livekit/components-react';
55
import { ChatEntry } from '@/components/livekit/chat-entry';
6+
import { ChatIndicator } from '@/components/livekit/chat-indicator';
67

78
const MotionContainer = motion.create('div');
89
const MotionChatEntry = motion.create(ChatEntry);
@@ -58,31 +59,35 @@ export function ChatTranscript({
5859
messages = [],
5960
...props
6061
}: ChatTranscriptProps & Omit<HTMLMotionProps<'div'>, 'ref'>) {
62+
const { state: agentState } = useVoiceAssistant();
6163
return (
62-
<AnimatePresence>
63-
{!hidden && (
64-
<MotionContainer {...CONTAINER_MOTION_PROPS} {...props}>
65-
{messages.map((receivedMessage) => {
66-
const { id, timestamp, from, message } = receivedMessage;
67-
const locale = navigator?.language ?? 'en-US';
68-
const messageOrigin = from?.isLocal ? 'local' : 'remote';
69-
const hasBeenEdited =
70-
receivedMessage.type === 'chatMessage' && !!receivedMessage.editTimestamp;
64+
<>
65+
<AnimatePresence>
66+
{!hidden && (
67+
<MotionContainer {...CONTAINER_MOTION_PROPS} {...props}>
68+
{messages.map((receivedMessage) => {
69+
const { id, timestamp, from, message } = receivedMessage;
70+
const locale = navigator?.language ?? 'en-US';
71+
const messageOrigin = from?.isLocal ? 'local' : 'remote';
72+
const hasBeenEdited =
73+
receivedMessage.type === 'chatMessage' && !!receivedMessage.editTimestamp;
7174

72-
return (
73-
<MotionChatEntry
74-
key={id}
75-
locale={locale}
76-
timestamp={timestamp}
77-
message={message}
78-
messageOrigin={messageOrigin}
79-
hasBeenEdited={hasBeenEdited}
80-
{...MESSAGE_MOTION_PROPS}
81-
/>
82-
);
83-
})}
84-
</MotionContainer>
85-
)}
86-
</AnimatePresence>
75+
return (
76+
<MotionChatEntry
77+
key={id}
78+
locale={locale}
79+
timestamp={timestamp}
80+
message={message}
81+
messageOrigin={messageOrigin}
82+
hasBeenEdited={hasBeenEdited}
83+
{...MESSAGE_MOTION_PROPS}
84+
/>
85+
);
86+
})}
87+
<ChatIndicator agentState={agentState} />
88+
</MotionContainer>
89+
)}
90+
</AnimatePresence>
91+
</>
8792
);
8893
}

components/demos/ChatIndicator.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Container } from '../docs/container';
2+
import { ChatIndicator } from '../livekit/chat-indicator';
3+
4+
export default function ChatIndicatorDemo() {
5+
return (
6+
<Container componentName="ChatIndicator">
7+
<ChatIndicator agentState="thinking" />
8+
</Container>
9+
);
10+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { AnimatePresence, motion } from 'motion/react';
2+
import { type AgentState } from '@livekit/components-react';
3+
import { cn } from '@/lib/utils';
4+
5+
const motionAnimationProps = {
6+
variants: {
7+
hidden: {
8+
opacity: 0,
9+
scale: 0.1,
10+
transition: {
11+
duration: 0.1,
12+
ease: 'linear',
13+
},
14+
},
15+
visible: {
16+
opacity: [0.5, 1],
17+
scale: [1, 1.2],
18+
transition: {
19+
type: 'spring',
20+
bounce: 0,
21+
duration: 0.5,
22+
repeat: Infinity,
23+
repeatType: 'mirror' as const,
24+
},
25+
},
26+
},
27+
initial: 'hidden',
28+
animate: 'visible',
29+
exit: 'hidden',
30+
};
31+
32+
export interface ChatIndicatorProps {
33+
agentState?: AgentState;
34+
className?: string;
35+
}
36+
37+
export function ChatIndicator({ agentState, className }: ChatIndicatorProps) {
38+
return (
39+
<AnimatePresence>
40+
{agentState === 'thinking' && (
41+
<motion.span
42+
{...motionAnimationProps}
43+
transition={{ duration: 0.1, ease: 'linear' }}
44+
className={cn('bg-muted-foreground inline-block size-2.5 rounded-full', className)}
45+
/>
46+
)}
47+
</AnimatePresence>
48+
);
49+
}

0 commit comments

Comments
 (0)