Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions app-config.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,4 @@
export interface AppConfig {
pageTitle: string;
pageDescription: string;
companyName: string;

supportsChatInput: boolean;
supportsVideoInput: boolean;
supportsScreenShare: boolean;
isPreConnectBufferEnabled: boolean;

logo: string;
startButtonText: string;
accent?: string;
logoDark?: string;
accentDark?: string;

sandboxId?: string;
agentName?: string;
}
import type { AppConfig } from './lib/types';

export const APP_CONFIG_DEFAULTS: AppConfig = {
companyName: 'LiveKit',
Expand All @@ -33,4 +15,6 @@ export const APP_CONFIG_DEFAULTS: AppConfig = {
logoDark: '/lk-logo-dark.svg',
accentDark: '#1fd5f9',
startButtonText: 'Start call',

agentName: undefined,
};
5 changes: 2 additions & 3 deletions app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { headers } from 'next/headers';
import { getAppConfig } from '@/lib/utils';

interface LayoutProps {
interface AppLayoutProps {
children: React.ReactNode;
}

export default async function Layout({ children }: LayoutProps) {
export default async function AppLayout({ children }: AppLayoutProps) {
const hdrs = await headers();
const { companyName, logo, logoDark } = await getAppConfig(hdrs);

Expand Down Expand Up @@ -39,7 +39,6 @@ export default async function Layout({ children }: LayoutProps) {
</a>
</span>
</header>

{children}
</>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(app)/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export default async function Image() {
gap: 10,
}}
>
{/* eslint-disable-next-line jsx-a11y/alt-text */}
{/* eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text */}
<img src={wordmarkSrcBase64} width={wordmarkSize.width} height={wordmarkSize.height} />
</div>
{/* logo */}
Expand All @@ -179,7 +179,7 @@ export default async function Image() {
gap: 10,
}}
>
{/* eslint-disable-next-line jsx-a11y/alt-text */}
{/* eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text */}
<img src={logoSrcBase64} width={logoSize.width} height={logoSize.height} />
</div>
{/* title */}
Expand Down
2 changes: 1 addition & 1 deletion app/(app)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { headers } from 'next/headers';
import { App } from '@/components/app/app';
import { App } from '@/components/app';
import { getAppConfig } from '@/lib/utils';

export default async function Page() {
Expand Down
14 changes: 7 additions & 7 deletions app/api/connection-details/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import { NextResponse } from 'next/server';
import { AccessToken, type AccessTokenOptions, type VideoGrant } from 'livekit-server-sdk';
import { RoomConfiguration } from '@livekit/protocol';

type ConnectionDetails = {
serverUrl: string;
roomName: string;
participantName: string;
participantToken: string;
};

// NOTE: you are expected to define the following environment variables in `.env.local`:
const API_KEY = process.env.LIVEKIT_API_KEY;
const API_SECRET = process.env.LIVEKIT_API_SECRET;
Expand All @@ -17,6 +10,13 @@ const LIVEKIT_URL = process.env.LIVEKIT_URL;
// don't cache the results
export const revalidate = 0;

export type ConnectionDetails = {
serverUrl: string;
roomName: string;
participantName: string;
participantToken: string;
};

export async function POST(req: Request) {
try {
if (LIVEKIT_URL === undefined) {
Expand Down
12 changes: 12 additions & 0 deletions app/components/Container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cn } from '@/lib/utils';

interface ContainerProps {
children: React.ReactNode;
className?: string;
}

export function Container({ children, className }: ContainerProps) {
return (
<div className={cn('relative space-y-4 rounded-lg border p-4', className)}>{children}</div>
);
}
33 changes: 33 additions & 0 deletions app/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/utils';

export function Tabs() {
const pathname = usePathname();

return (
<div className="flex flex-row justify-between border-b">
<Link
href="/components/base"
className={cn(
'text-fg0 -mb-px cursor-pointer px-4 pt-2 text-xl font-bold tracking-tight uppercase',
pathname === '/components/base' && 'bg-background rounded-t-lg border-t border-r border-l'
)}
>
Base components
</Link>
<Link
href="/components/livekit"
className={cn(
'text-fg0 -mb-px cursor-pointer px-4 py-2 text-xl font-bold tracking-tight uppercase',
pathname === '/components/livekit' &&
'bg-background rounded-t-lg border-t border-r border-l'
)}
>
LiveKit components
</Link>
</div>
);
}
132 changes: 132 additions & 0 deletions app/components/base/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { PlusIcon } from '@phosphor-icons/react/dist/ssr';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Toggle } from '@/components/ui/toggle';
import { Container } from '../Container';

const buttonVariants = ['default', 'secondary', 'outline', 'ghost', 'link', 'destructive'] as const;
const toggleVariants = ['default', 'outline'] as const;
const alertVariants = ['default', 'destructive'] as const;

export default function Base() {
return (
<>
{/* Button */}
<Container>
<h3 className="text-muted-foreground text-sm">A button component.</h3>
<div className="space-y-2">
{buttonVariants.map((variant) => (
<div key={variant}>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
<div className="grid w-full grid-cols-4 gap-2">
<div>
<Button variant={variant} size="sm">
Size sm
</Button>
</div>
<div>
<Button variant={variant}>Size default</Button>
</div>
<div>
<Button variant={variant} size="lg">
Size lg
</Button>
</div>
<div>
<Button variant={variant} size="icon">
<PlusIcon size={16} weight="bold" />
</Button>
</div>
</div>
</div>
))}
</div>
</Container>

{/* Toggle */}
<Container>
<h3 className="text-muted-foreground text-sm">A toggle component.</h3>
<div className="space-y-2">
{toggleVariants.map((variant) => (
<div key={variant}>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
<div className="grid w-full grid-cols-3 gap-2">
<div>
<Toggle key={variant} variant={variant} size="sm">
Size sm
</Toggle>
</div>
<div>
<Toggle key={variant} variant={variant}>
Size default
</Toggle>
</div>
<div>
<Toggle key={variant} variant={variant} size="lg">
Size lg
</Toggle>
</div>
</div>
</div>
))}
</div>
</Container>

{/* Alert */}
<Container>
<h3 className="text-muted-foreground text-sm">An alert component.</h3>
<div className="space-y-6">
{alertVariants.map((variant) => (
<div key={variant}>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
<Alert key={variant} variant={variant}>
<AlertTitle>Alert {variant} title</AlertTitle>
<AlertDescription>This is a {variant} alert description.</AlertDescription>
</Alert>
</div>
))}
</div>
</Container>

{/* Select */}
<Container>
<h3 className="text-muted-foreground text-sm">A select component.</h3>
<div className="grid w-full grid-cols-2 gap-2">
<div>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size default</h4>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a track" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Track 1</SelectItem>
<SelectItem value="2">Track 2</SelectItem>
<SelectItem value="3">Track 3</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<h3 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size sm</h3>
<Select>
<SelectTrigger size="sm">
<SelectValue placeholder="Select a track" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">Track 1</SelectItem>
<SelectItem value="2">Track 2</SelectItem>
<SelectItem value="3">Track 3</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</Container>
</>
);
}
24 changes: 24 additions & 0 deletions app/components/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import { headers } from 'next/headers';
import { Tabs } from '@/app/components/Tabs';
import { Provider } from '@/components/provider';
import { cn, getAppConfig } from '@/lib/utils';

export default async function ComponentsLayout({ children }: { children: React.ReactNode }) {
const hdrs = await headers();
const appConfig = await getAppConfig(hdrs);
return (
<div className="mx-auto min-h-svh max-w-3xl space-y-8 px-4 py-8">
<header className="flex flex-col gap-1">
<h1 className="text-3xl font-bold tracking-tight">Quick Start UI overview</h1>
<p className="text-muted-foreground">
A quick start UI overview for the LiveKit Voice Assistant.
</p>
</header>
<Tabs />
<Provider appConfig={appConfig}>
<main className="flex w-full flex-1 flex-col items-stretch gap-8">{children}</main>
</Provider>
</div>
);
}
66 changes: 66 additions & 0 deletions app/components/livekit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Track } from 'livekit-client';
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
import { DeviceSelect } from '@/components/livekit/device-select';
import { TrackToggle } from '@/components/livekit/track-toggle';
import { Container } from '../Container';

export default function LiveKit() {
return (
<>
{/* Device select */}
<Container>
<div className="flex items-center justify-between">
<h3 className="text-muted-foreground text-sm">A device select component.</h3>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size default</h4>
<DeviceSelect kind="audioinput" />
</div>
<div>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size sm</h4>
<DeviceSelect size="sm" kind="audioinput" />
</div>
</div>
</Container>

{/* Track toggle */}
<Container>
<div className="flex items-center justify-between">
<h3 className="text-muted-foreground text-sm">A track toggle component.</h3>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">
Track.Source.Microphone
</h4>
<TrackToggle variant="outline" source={Track.Source.Microphone} />
</div>
<div>
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">
Track.Source.Camera
</h4>
<TrackToggle variant="outline" source={Track.Source.Camera} />
</div>
</div>
</Container>

{/* Agent control bar */}
<Container>
<div className="flex items-center justify-between">
<h3 className="text-muted-foreground text-sm">A control bar component.</h3>
</div>
<div className="relative flex items-center justify-center">
<AgentControlBar
className="w-full"
capabilities={{
supportsChatInput: true,
supportsVideoInput: true,
supportsScreenShare: true,
}}
/>
</div>
</Container>
</>
);
}
5 changes: 5 additions & 0 deletions app/components/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from 'next/navigation';

export default function Components() {
return redirect('/components/base');
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading