Skip to content

Commit 71fac98

Browse files
chore: migrate to useSession hook
1 parent 11fc465 commit 71fac98

File tree

13 files changed

+189
-259
lines changed

13 files changed

+189
-259
lines changed

app/ui/layout.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
1-
import * as React from 'react';
1+
import { useEffect, useState } from 'react';
22
import { headers } from 'next/headers';
3-
import { SessionProvider } from '@/components/app/session-provider';
3+
import { TokenSource } from 'livekit-client';
4+
import {
5+
RoomAudioRenderer,
6+
type Session,
7+
// useAgent,
8+
// useSessionMessages,
9+
SessionProvider,
10+
StartAudio,
11+
// General media control / visualization:
12+
// VideoTrack,
13+
// StartAudio,
14+
// RoomAudioRenderer,
15+
// useMediaDeviceSelect,
16+
// useTrackToggle,
17+
// Agent specific hooks / components:
18+
useSession,
19+
} from '@livekit/components-react';
20+
import type { AppConfig } from '@/app-config';
421
import { getAppConfig } from '@/lib/utils';
522

23+
interface ComponentProps {
24+
appConfig: AppConfig;
25+
children: React.ReactNode;
26+
}
27+
28+
function Layout({ appConfig, children }: ComponentProps) {
29+
const tokenSource = TokenSource.sandboxTokenServer(appConfig.sandboxId);
30+
const session = useSession(tokenSource, { agentName: 'voice ai quickstart' });
31+
32+
return <SessionProvider session={session}>{children}</SessionProvider>;
33+
}
34+
635
export default async function ComponentsLayout({ children }: { children: React.ReactNode }) {
736
const hdrs = await headers();
837
const appConfig = await getAppConfig(hdrs);
938

1039
return (
11-
<SessionProvider appConfig={appConfig}>
40+
<Layout appConfig={appConfig}>
1241
<div className="bg-muted/20 min-h-svh p-8">
1342
<div className="mx-auto max-w-3xl space-y-8">
1443
<header className="space-y-2">
1544
<h1 className="text-5xl font-bold tracking-tight">LiveKit UI</h1>
1645
<p className="text-muted-foreground max-w-80 leading-tight text-pretty">
17-
A set of UI components for building LiveKit-powered voice experiences.
46+
A set of UI Layouts for building LiveKit-powered voice experiences.
1847
</p>
1948
<p className="text-muted-foreground max-w-prose text-balance">
2049
Built with{' '}
@@ -37,6 +66,6 @@ export default async function ComponentsLayout({ children }: { children: React.R
3766
<main className="space-y-20">{children}</main>
3867
</div>
3968
</div>
40-
</SessionProvider>
69+
</Layout>
4170
);
4271
}

components/app/app.tsx

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,74 @@
11
'use client';
22

3-
import { RoomAudioRenderer, StartAudio } from '@livekit/components-react';
3+
import { useMemo } from 'react';
4+
import { TokenSource } from 'livekit-client';
5+
import {
6+
RoomAudioRenderer,
7+
SessionProvider,
8+
StartAudio,
9+
useSession,
10+
} from '@livekit/components-react';
411
import type { AppConfig } from '@/app-config';
5-
import { SessionProvider } from '@/components/app/session-provider';
612
import { ViewController } from '@/components/app/view-controller';
713
import { Toaster } from '@/components/livekit/toaster';
14+
import { ConnectionProvider } from '@/hooks/useConnection';
15+
import { useDebugMode } from '@/hooks/useDebug';
16+
17+
const IN_DEVELOPMENT = process.env.NODE_ENV !== 'production';
818

919
interface AppProps {
1020
appConfig: AppConfig;
1121
}
1222

1323
export function App({ appConfig }: AppProps) {
24+
// useDebugMode({ enabled: IN_DEVELOPMENT });
25+
26+
const tokenSource = useMemo(() => {
27+
if (process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT) {
28+
return TokenSource.custom(async () => {
29+
const url = new URL(
30+
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? '/api/connection-details',
31+
window.location.origin
32+
);
33+
34+
try {
35+
const res = await fetch(url.toString(), {
36+
method: 'POST',
37+
headers: {
38+
'Content-Type': 'application/json',
39+
'X-Sandbox-Id': appConfig.sandboxId ?? '',
40+
},
41+
body: JSON.stringify({
42+
room_config: appConfig.agentName
43+
? {
44+
agents: [{ agent_name: appConfig.agentName }],
45+
}
46+
: undefined,
47+
}),
48+
});
49+
return await res.json();
50+
} catch (error) {
51+
console.error('Error fetching connection details:', error);
52+
throw new Error('Error fetching connection details!');
53+
}
54+
});
55+
}
56+
57+
return TokenSource.endpoint('/api/connection-details');
58+
}, [appConfig]);
59+
60+
const session = useSession(tokenSource, { agentName: 'voice ai quickstart' });
61+
1462
return (
15-
<SessionProvider appConfig={appConfig}>
16-
<main className="grid h-svh grid-cols-1 place-content-center">
17-
<ViewController />
18-
</main>
19-
<StartAudio label="Start Audio" />
20-
<RoomAudioRenderer />
21-
<Toaster />
63+
<SessionProvider session={session}>
64+
<ConnectionProvider>
65+
<main className="grid h-svh grid-cols-1 place-content-center">
66+
<ViewController appConfig={appConfig} />
67+
</main>
68+
<StartAudio label="Start Audio" />
69+
<RoomAudioRenderer />
70+
<Toaster />
71+
</ConnectionProvider>
2272
</SessionProvider>
2373
);
2474
}

components/app/chat-transcript.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { AnimatePresence, type HTMLMotionProps, motion } from 'motion/react';
4-
import { type ReceivedChatMessage } from '@livekit/components-react';
4+
import { type ReceivedMessage } from '@livekit/components-react';
55
import { ChatEntry } from '@/components/livekit/chat-entry';
66

77
const MotionContainer = motion.create('div');
@@ -50,7 +50,7 @@ const MESSAGE_MOTION_PROPS = {
5050

5151
interface ChatTranscriptProps {
5252
hidden?: boolean;
53-
messages?: ReceivedChatMessage[];
53+
messages?: ReceivedMessage[];
5454
}
5555

5656
export function ChatTranscript({
@@ -62,10 +62,9 @@ export function ChatTranscript({
6262
<AnimatePresence>
6363
{!hidden && (
6464
<MotionContainer {...CONTAINER_MOTION_PROPS} {...props}>
65-
{messages.map(({ id, timestamp, from, message, editTimestamp }: ReceivedChatMessage) => {
65+
{messages.map(({ id, timestamp, from, message }: ReceivedMessage) => {
6666
const locale = navigator?.language ?? 'en-US';
6767
const messageOrigin = from?.isLocal ? 'local' : 'remote';
68-
const hasBeenEdited = !!editTimestamp;
6968

7069
return (
7170
<MotionChatEntry
@@ -74,7 +73,6 @@ export function ChatTranscript({
7473
timestamp={timestamp}
7574
message={message}
7675
messageOrigin={messageOrigin}
77-
hasBeenEdited={hasBeenEdited}
7876
{...MESSAGE_MOTION_PROPS}
7977
/>
8078
);

components/app/preconnect-message.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { AnimatePresence, motion } from 'motion/react';
4-
import { type ReceivedChatMessage } from '@livekit/components-react';
4+
import { type ReceivedMessage } from '@livekit/components-react';
55
import { ShimmerText } from '@/components/livekit/shimmer-text';
66
import { cn } from '@/lib/utils';
77

@@ -32,7 +32,7 @@ const VIEW_MOTION_PROPS = {
3232
};
3333

3434
interface PreConnectMessageProps {
35-
messages?: ReceivedChatMessage[];
35+
messages?: ReceivedMessage[];
3636
className?: string;
3737
}
3838

components/app/session-provider.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

components/app/session-view.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import React, { useEffect, useRef, useState } from 'react';
44
import { motion } from 'motion/react';
5+
import { useSessionContext, useSessionMessages } from '@livekit/components-react';
56
import type { AppConfig } from '@/app-config';
67
import { ChatTranscript } from '@/components/app/chat-transcript';
78
import { PreConnectMessage } from '@/components/app/preconnect-message';
@@ -10,15 +11,12 @@ import {
1011
AgentControlBar,
1112
type ControlBarControls,
1213
} from '@/components/livekit/agent-control-bar/agent-control-bar';
13-
import { useChatMessages } from '@/hooks/useChatMessages';
1414
import { useConnectionTimeout } from '@/hooks/useConnectionTimout';
15-
import { useDebugMode } from '@/hooks/useDebug';
1615
import { cn } from '@/lib/utils';
1716
import { ScrollArea } from '../livekit/scroll-area/scroll-area';
1817

1918
const MotionBottom = motion.create('div');
2019

21-
const IN_DEVELOPMENT = process.env.NODE_ENV !== 'production';
2220
const BOTTOM_VIEW_MOTION_PROPS = {
2321
variants: {
2422
visible: {
@@ -66,10 +64,10 @@ export const SessionView = ({
6664
appConfig,
6765
...props
6866
}: React.ComponentProps<'section'> & SessionViewProps) => {
69-
useConnectionTimeout(200_000);
70-
useDebugMode({ enabled: IN_DEVELOPMENT });
67+
useConnectionTimeout(20_000);
7168

72-
const messages = useChatMessages();
69+
const session = useSessionContext();
70+
const { messages } = useSessionMessages(session);
7371
const [chatOpen, setChatOpen] = useState(false);
7472
const scrollAreaRef = useRef<HTMLDivElement>(null);
7573

components/app/view-controller.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
'use client';
22

3-
import { useRef } from 'react';
4-
import { AnimatePresence, motion } from 'motion/react';
5-
import { useRoomContext } from '@livekit/components-react';
6-
import { useSession } from '@/components/app/session-provider';
3+
import { useCallback } from 'react';
4+
import { AnimatePresence, type AnimationDefinition, motion } from 'motion/react';
5+
import { useSessionContext } from '@livekit/components-react';
6+
import { AppConfig } from '@/app-config';
77
import { SessionView } from '@/components/app/session-view';
88
import { WelcomeView } from '@/components/app/welcome-view';
9+
import { useConnection } from '@/hooks/useConnection';
910

1011
const MotionWelcomeView = motion.create(WelcomeView);
1112
const MotionSessionView = motion.create(SessionView);
@@ -28,34 +29,36 @@ const VIEW_MOTION_PROPS = {
2829
},
2930
};
3031

31-
export function ViewController() {
32-
const room = useRoomContext();
33-
const isSessionActiveRef = useRef(false);
34-
const { appConfig, isSessionActive, startSession } = useSession();
32+
interface ViewControllerProps {
33+
appConfig: AppConfig;
34+
}
3535

36-
// animation handler holds a reference to stale isSessionActive value
37-
isSessionActiveRef.current = isSessionActive;
36+
export function ViewController({ appConfig }: ViewControllerProps) {
37+
const session = useSessionContext();
38+
const { isConnected, connect } = useConnection();
3839

39-
// disconnect room after animation completes
40-
const handleAnimationComplete = () => {
41-
if (!isSessionActiveRef.current && room.state !== 'disconnected') {
42-
room.disconnect();
43-
}
44-
};
40+
const handleAnimationComplete = useCallback(
41+
(definition: AnimationDefinition) => {
42+
if (definition === 'hidden') {
43+
session.end();
44+
}
45+
},
46+
[session]
47+
);
4548

4649
return (
4750
<AnimatePresence mode="wait">
4851
{/* Welcome screen */}
49-
{!isSessionActive && (
52+
{!isConnected && (
5053
<MotionWelcomeView
5154
key="welcome"
5255
{...VIEW_MOTION_PROPS}
53-
startButtonText={appConfig.startButtonText}
54-
onStartCall={startSession}
56+
startButtonText={appConfig?.startButtonText ?? ''}
57+
onStartCall={connect}
5558
/>
5659
)}
5760
{/* Session view */}
58-
{isSessionActive && (
61+
{isConnected && (
5962
<MotionSessionView
6063
key="session-view"
6164
{...VIEW_MOTION_PROPS}

0 commit comments

Comments
 (0)