Skip to content

Commit e246b6b

Browse files
feat: add app config object (#18)
1 parent 8925bb0 commit e246b6b

File tree

13 files changed

+218
-50
lines changed

13 files changed

+218
-50
lines changed

app-config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { AppConfig } from './lib/types';
2+
3+
export const APP_CONFIG: AppConfig = {
4+
companyName: 'LiveKit',
5+
pageTitle: 'Voice Assistant',
6+
pageDescription: 'A voice assistant built with LiveKit',
7+
8+
suportsChatInput: false,
9+
suportsVideoInput: false,
10+
suportsScreenShare: false,
11+
12+
logo: '/lk-logo.svg',
13+
accent: '#002cf2',
14+
logoDark: '/lk-logo-dark.svg',
15+
accentDark: '#1fd5f9',
16+
startButtonText: 'Start call',
17+
};

app/(app)/layout.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { getAppConfig } from '@/lib/utils';
2+
3+
interface AppLayoutProps {
4+
children: React.ReactNode;
5+
}
6+
7+
export default async function AppLayout({ children }: AppLayoutProps) {
8+
const { companyName, logo, logoDark } = await getAppConfig();
9+
return (
10+
<>
11+
<header className="fixed top-0 left-0 z-50 hidden w-full flex-row justify-between p-6 md:flex">
12+
<a
13+
target="_blank"
14+
rel="noopener noreferrer"
15+
href="https://livekit.io"
16+
className="scale-100 transition-transform duration-300 hover:scale-110"
17+
>
18+
<img src={logo} alt={`${companyName} Logo`} className="block size-6 dark:hidden" />
19+
<img
20+
src={logoDark ?? logo}
21+
alt={`${companyName} Logo`}
22+
className="hidden size-6 dark:block"
23+
/>
24+
</a>
25+
<span className="text-foreground font-mono text-xs font-bold tracking-wider uppercase">
26+
Built with{' '}
27+
<a
28+
target="_blank"
29+
rel="noopener noreferrer"
30+
href="https://github.com/livekit/agents"
31+
className="underline underline-offset-4"
32+
>
33+
LiveKit Agents
34+
</a>
35+
</span>
36+
</header>
37+
{children}
38+
</>
39+
);
40+
}

app/(app)/page.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Metadata, ResolvingMetadata } from 'next';
2+
import { App } from '@/components/app';
3+
import { getAppConfig } from '@/lib/utils';
4+
5+
export async function generateMetadata(_props: any, parent: ResolvingMetadata): Promise<Metadata> {
6+
const { pageTitle, pageDescription } = await getAppConfig();
7+
const parentMetadata = await parent;
8+
9+
return {
10+
...parentMetadata,
11+
title: pageTitle,
12+
description: pageDescription + '\n\nBuilt with LiveKit Agents.',
13+
} as Metadata;
14+
}
15+
16+
export default async function Page() {
17+
const appConfig = await getAppConfig();
18+
19+
return <App appConfig={appConfig} />;
20+
}

app/layout.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from 'next';
22
import { Public_Sans } from 'next/font/google';
33
import localFont from 'next/font/local';
44
import { ApplyThemeScript, ThemeToggle } from '@/components/theme-toggle';
5+
import { getAppConfig } from '@/lib/utils';
56
import './globals.css';
67

78
const publicSans = Public_Sans({
@@ -36,18 +37,28 @@ const commitMono = localFont({
3637
});
3738

3839
export const metadata: Metadata = {
39-
title: 'Create Next App',
40-
description: 'Generated by create next app',
40+
title: 'Voice Assistant',
41+
description: 'A voice assistant built with LiveKit',
4142
};
4243

4344
interface RootLayoutProps {
4445
children: React.ReactNode;
4546
}
4647

47-
export default function RootLayout({ children }: RootLayoutProps) {
48+
export default async function RootLayout({ children }: RootLayoutProps) {
49+
const { accent, accentDark } = await getAppConfig();
50+
51+
const styles = [
52+
accent ? `:root { --primary: ${accent}; }` : '',
53+
accentDark ? `.dark { --primary: ${accentDark}; }` : '',
54+
]
55+
.filter(Boolean)
56+
.join('\n');
57+
4858
return (
4959
<html lang="en" suppressHydrationWarning className="scroll-smooth">
5060
<head>
61+
{styles && <style>{styles}</style>}
5162
<ApplyThemeScript />
5263
</head>
5364
<body

app/page.tsx renamed to components/app.tsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,21 @@ import SessionView from '@/components/session-view';
88
import { Toaster } from '@/components/ui/sonner';
99
import { Welcome } from '@/components/welcome';
1010
import useConnectionDetails from '@/hooks/useConnectionDetails';
11+
import type { AppConfig } from '@/lib/types';
1112

12-
export default function Home() {
13+
interface AppProps {
14+
appConfig: AppConfig;
15+
}
16+
17+
export function App({ appConfig }: AppProps) {
1318
const [sessionStarted, setSessionStarted] = React.useState(false);
19+
const { suportsChatInput, suportsVideoInput, suportsScreenShare, startButtonText } = appConfig;
20+
21+
const capabilities = {
22+
suportsChatInput,
23+
suportsVideoInput,
24+
suportsScreenShare,
25+
};
1426

1527
const connectionDetails = useConnectionDetails();
1628

@@ -55,35 +67,14 @@ export default function Home() {
5567

5668
return (
5769
<>
58-
<header className="fixed top-0 left-0 z-50 hidden w-full flex-row justify-between p-6 md:flex">
59-
<a
60-
target="_blank"
61-
rel="noopener noreferrer"
62-
href="https://livekit.io"
63-
className="scale-100 transition-transform duration-300 hover:scale-110"
64-
>
65-
<img src="/lk-logo.svg" alt="LiveKit Logo" className="size-6" />
66-
</a>
67-
<span className="text-foreground font-mono text-xs font-bold tracking-wider uppercase">
68-
Built with{' '}
69-
<a
70-
target="_blank"
71-
rel="noopener noreferrer"
72-
href="https://github.com/livekit/agents"
73-
className="underline underline-offset-4"
74-
>
75-
LiveKit Agents
76-
</a>
77-
</span>
78-
</header>
7970
{sessionStarted ? (
8071
<RoomContext.Provider value={room}>
81-
<SessionView />
72+
<SessionView capabilities={capabilities} />
8273
<RoomAudioRenderer />
8374
<StartAudio label="Start Audio" />
8475
</RoomContext.Provider>
8576
) : (
86-
<Welcome onStartCall={() => setSessionStarted(true)} />
77+
<Welcome startButtonText={startButtonText} onStartCall={() => setSessionStarted(true)} />
8778
)}
8879
<Toaster />
8980
</>

components/livekit/agent-control-bar/agent-control-bar.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ChatTextIcon, PhoneDisconnectIcon } from '@phosphor-icons/react/dist/ss
77
import { ChatInput } from '@/components/livekit/chat/chat-input';
88
import { Button } from '@/components/ui/button';
99
import { Toggle } from '@/components/ui/toggle';
10+
import { AppConfig } from '@/lib/types';
1011
import { cn } from '@/lib/utils';
1112
import { DeviceSelect } from '../device-select';
1213
import { TrackToggle } from '../track-toggle';
@@ -15,6 +16,7 @@ import { UseAgentControlBarProps, useAgentControlBar } from './hooks/use-agent-c
1516
export interface AgentControlBarProps
1617
extends React.HTMLAttributes<HTMLDivElement>,
1718
UseAgentControlBarProps {
19+
capabilities: AppConfig['capabilities'];
1820
onSendMessage?: (message: string) => Promise<void>;
1921
onChatOpenChange?: (open: boolean) => void;
2022
}
@@ -25,6 +27,7 @@ export interface AgentControlBarProps
2527
export function AgentControlBar({
2628
controls,
2729
saveUserChoices = true,
30+
capabilities,
2831
onDeviceError,
2932
onSendMessage,
3033
onChatOpenChange,
@@ -69,18 +72,20 @@ export function AgentControlBar({
6972
)}
7073
{...props}
7174
>
72-
<div
73-
inert={!chatOpen}
74-
className={cn(
75-
'overflow-hidden transition-[height] duration-300 ease-out',
76-
chatOpen ? 'h-[57px]' : 'h-0'
77-
)}
78-
>
79-
<div className="flex h-8 w-full">
80-
<ChatInput onSend={handleSendMessage} disabled={isSendingMessage} className="w-full" />
75+
{capabilities.suportsChatInput && (
76+
<div
77+
inert={!chatOpen}
78+
className={cn(
79+
'overflow-hidden transition-[height] duration-300 ease-out',
80+
chatOpen ? 'h-[57px]' : 'h-0'
81+
)}
82+
>
83+
<div className="flex h-8 w-full">
84+
<ChatInput onSend={handleSendMessage} disabled={isSendingMessage} className="w-full" />
85+
</div>
86+
<hr className="my-3" />
8187
</div>
82-
<hr className="my-3" />
83-
</div>
88+
)}
8489

8590
<div className="flex flex-row justify-between gap-1">
8691
<div className="flex w-full gap-1">
@@ -109,7 +114,7 @@ export function AgentControlBar({
109114
</div>
110115
)}
111116

112-
{visibleControls.camera && (
117+
{capabilities.suportsVideoInput && visibleControls.camera && (
113118
<div className="flex items-center gap-0">
114119
<TrackToggle
115120
className="peer relative w-auto md:rounded-r-none md:border-r-0"
@@ -125,7 +130,7 @@ export function AgentControlBar({
125130
</div>
126131
)}
127132

128-
{visibleControls.screenShare && (
133+
{capabilities.suportsScreenShare && visibleControls.screenShare && (
129134
<div className="flex items-center gap-0">
130135
<TrackToggle className="relative w-auto" source={Track.Source.ScreenShare} />
131136
</div>

components/livekit/chat/chat-entry.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ export const ChatEntry = ({
3131
<li
3232
data-lk-message-origin={messageOrigin}
3333
title={time.toLocaleTimeString(locale, { timeStyle: 'full' })}
34-
className={cn('flex flex-col gap-0.5', className)}
34+
className={cn('group flex flex-col gap-0.5', className)}
3535
{...props}
3636
>
3737
{(!hideTimestamp || !hideName || hasBeenEdited) && (
3838
<span className="text-muted-foreground flex text-sm">
3939
{!hideName && <strong className="mt-2">{name}</strong>}
4040

4141
{!hideTimestamp && (
42-
<span className="align-self-end ml-auto">
42+
<span className="align-self-end ml-auto opacity-0 transition-opacity ease-linear group-hover:opacity-100">
4343
{hasBeenEdited && '*'}
4444
{time.toLocaleTimeString(locale, { timeStyle: 'short' })}
4545
</span>

components/session-view.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ import { ChatEntry } from '@/components/livekit/chat/chat-entry';
88
import { ChatMessageView } from '@/components/livekit/chat/chat-message-view';
99
import useChatAndTranscription from '@/hooks/useChatAndTranscription';
1010
import { useDebugMode } from '@/hooks/useDebug';
11+
import { AppConfig } from '@/lib/types';
1112
import { cn } from '@/lib/utils';
1213

13-
export default function SessionView() {
14+
interface SessionViewProp {
15+
capabilities: AppConfig['capabilities'];
16+
}
17+
18+
export default function SessionView({ capabilities }: SessionViewProp) {
1419
const [chatOpen, setChatOpen] = React.useState(false);
1520
const { messages, send } = useChatAndTranscription();
1621

@@ -27,6 +32,7 @@ export default function SessionView() {
2732
<div className="space-y-3 whitespace-pre-wrap">
2833
{messages.map((message: ReceivedChatMessage) => (
2934
<ChatEntry
35+
hideName
3036
key={message.id}
3137
entry={message}
3238
className="appear-fade-in duration-2000 ease-out"
@@ -69,7 +75,11 @@ export default function SessionView() {
6975
</p>
7076
</div>
7177

72-
<AgentControlBar onChatOpenChange={setChatOpen} onSendMessage={handleSendMessage} />
78+
<AgentControlBar
79+
capabilities={capabilities}
80+
onChatOpenChange={setChatOpen}
81+
onSendMessage={handleSendMessage}
82+
/>
7383
</div>
7484
{/* skrim */}
7585
<div className="from-background border-background absolute top-0 left-0 h-12 w-full -translate-y-full border-b-8 bg-gradient-to-t to-transparent" />

components/ui/button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { cn } from '@/lib/utils';
55

66
const buttonVariants = cva(
77
[
8-
'inline-flex items-center justify-center gap-2 shrink-0 rounded-md text-sm font-medium whitespace-nowrap cursor-pointer outline-none transition-colors duration-300',
8+
'text-xs font-bold tracking-wider uppercase whitespace-nowrap',
9+
'inline-flex items-center justify-center gap-2 shrink-0 rounded-md cursor-pointer outline-none transition-colors duration-300',
910
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
1011
'disabled:pointer-events-none disabled:opacity-50',
1112
'aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40 ',

components/welcome.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { CodeBlockIcon } from '@phosphor-icons/react/dist/ssr';
2-
import { Button } from './ui/button';
2+
import { Button } from '@/components/ui/button';
33

4-
export function Welcome({ onStartCall }: { onStartCall: () => void }) {
4+
interface WelcomeProps {
5+
startButtonText: string;
6+
onStartCall: () => void;
7+
}
8+
9+
export function Welcome({ startButtonText, onStartCall }: WelcomeProps) {
510
return (
611
<div className="mx-auto flex h-svh flex-col items-center justify-center text-center">
712
<CodeBlockIcon size={64} weight="bold" className="mx-auto mb-4" />
@@ -20,8 +25,8 @@ export function Welcome({ onStartCall }: { onStartCall: () => void }) {
2025
</a>
2126
.
2227
</p>
23-
<Button size="lg" onClick={onStartCall} className="mt-12 w-64 font-mono font-bold">
24-
START CALL
28+
<Button size="lg" onClick={onStartCall} className="mt-12 w-64 font-mono">
29+
{startButtonText}
2530
</Button>
2631
</div>
2732
);

0 commit comments

Comments
 (0)