Skip to content

Commit

Permalink
update frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Ocupe committed Sep 12, 2024
1 parent 0078b84 commit 703e9f7
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 99 deletions.
4 changes: 4 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Enviroment variables needed to connect to the LiveKit server.
LIVEKIT_API_KEY=<your_api_key>
LIVEKIT_API_SECRET=<your_api_secret>
LIVEKIT_SERVER_URL=wss://<project-subdomain>.livekit.cloud
63 changes: 17 additions & 46 deletions frontend/app/connection-details/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,38 @@ import {
AccessTokenOptions,
VideoGrant,
} from "livekit-server-sdk";
import { NextRequest, NextResponse } from "next/server";
import { NextResponse } from "next/server";

const API_KEY = process.env.LIVEKIT_API_KEY;
const API_SECRET = process.env.LIVEKIT_API_SECRET;
const LIVEKIT_URL = process.env.LIVEKIT_URL;
const LIVEKIT_SERVER_URL = process.env.LIVEKIT_SERVER_URL;

export async function GET(request: NextRequest) {
try {
// Parse query parameters
const roomName = request.nextUrl.searchParams.get("roomName");
const participantName = request.nextUrl.searchParams.get("participantName");
const metadata = request.nextUrl.searchParams.get("metadata") ?? "";
const region = request.nextUrl.searchParams.get("region");
const livekitServerUrl = region ? getLiveKitURL(region) : LIVEKIT_URL;
if (livekitServerUrl === undefined) {
throw new Error("Invalid region");
}

// if (typeof roomName !== "string") {
// return new NextResponse("Missing required query parameter: roomName", {
// status: 400,
// });
// }
// if (participantName === null) {
// return new NextResponse(
// "Missing required query parameter: participantName",
// { status: 400 }
// );
// }
export type ConnectionDetails = {
serverUrl: string;
roomName: string;
participantName: string;
participantToken: string;
};

export async function GET() {
try {
// Generate participant token
const participantIdentity = `voice_assistant_user_${Math.round(
Math.random() * 10_000
)}`;
const participantToken = await createParticipantToken(
{
identity: `${participantName}__${Math.round(Math.random() * 10000)}`,
name: "participantName",
metadata,
identity: participantIdentity,
},
"roomName"
);

// Return connection details
const data = {
serverUrl: livekitServerUrl,
roomName: roomName,
serverUrl: LIVEKIT_SERVER_URL,
roomName: "voice_assistant_room",
participantToken: participantToken,
participantName: participantName,
participantName: participantIdentity,
};
return NextResponse.json(data);
} catch (error) {
Expand All @@ -74,18 +60,3 @@ function createParticipantToken(
at.addGrant(grant);
return at.toJwt();
}

/**
* Get the LiveKit server URL for the given region.
*/
function getLiveKitURL(region: string | null): string {
let targetKey = "LIVEKIT_URL";
if (region) {
targetKey = `LIVEKIT_URL_${region}`.toUpperCase();
}
const url = process.env[targetKey];
if (!url) {
throw new Error(`${targetKey} is not defined`);
}
return url;
}
4 changes: 2 additions & 2 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>{children}</body>
<html lang="en" className="h-full">
<body className="h-full">{children}</body>
</html>
);
}
106 changes: 55 additions & 51 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,78 @@
"use client";

import "@livekit/components-styles";

import {
LiveKitRoom,
useToken,
useVoiceAssistant,
BarVisualizer,
RoomAudioRenderer,
VoiceAssistantControlBar,
} from "@livekit/components-react";
import { useCallback, useMemo, useState } from "react";
import { useCallback, useState } from "react";
import { MediaDeviceFailure } from "livekit-client";

function SimpleVoiceAssistant() {
const { state, audioTrack } = useVoiceAssistant();
return (
<BarVisualizer
state={state}
barCount={7}
trackRef={audioTrack}
style={{ width: "75vw", height: "300px" }}
/>
);
}
import type { ConnectionDetails } from "./connection-details/route";

export default function Page() {
const [shouldConnect, setShouldConnect] = useState(false);
const [details, setDetails] = useState<any>(undefined);
const [connectionDetails, updateConnectionDetails] = useState<
ConnectionDetails | undefined
>(undefined);

const handlePreJoinSubmit = useCallback(async () => {
const onConnectButtonClicked = useCallback(async () => {
const url = new URL(
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT!,
process.env.NEXT_PUBLIC_CONN_DETAILS_ENDPOINT ?? "connection-details",
window.location.origin
);
const connectionDetailsResp = await fetch(url.toString());
const connectionDetailsData = await connectionDetailsResp.json();
console.log({ connectionDetailsData });

setDetails(connectionDetailsData);
const response = await fetch(url.toString());
const connectionDetailsData = await response.json();
updateConnectionDetails(connectionDetailsData);
}, []);

const onDeviceFailure = (e?: MediaDeviceFailure) => {
console.error(e);
alert(
"Error acquiring camera or microphone permissions. Please make sure you grant the necessary permissions in your browser and reload the tab"
);
};

return (
<main data-lk-theme="default" className="">
{details ? (
<LiveKitRoom
audio={true}
token={details.participantToken}
connect={shouldConnect}
serverUrl={details.serverUrl}
onMediaDeviceFailure={onDeviceFailure}
onDisconnected={() => setShouldConnect(false)}
className=""
>
<div className="">
<SimpleVoiceAssistant />
<main data-lk-theme="default" className="h-full grid place-items-center">
<LiveKitRoom
token={connectionDetails?.participantToken}
serverUrl={connectionDetails?.serverUrl}
connect={connectionDetails !== undefined}
audio={true}
video={false}
onMediaDeviceFailure={onDeviceFailure}
onDisconnected={() => {
updateConnectionDetails(undefined);
}}
className="grid items-center grid-rows-[1fr_min-content]"
>
{connectionDetails ? (
<SimpleVoiceAssistant />
) : (
<div className="flex w-full justify-center">
<button
className="lk-button"
onClick={() => onConnectButtonClicked()}
>
Connect
</button>
</div>
<VoiceAssistantControlBar />
<RoomAudioRenderer />
</LiveKitRoom>
) : (
<button className="lk-button" onClick={() => handlePreJoinSubmit()}>
Connect
</button>
)}
)}
<VoiceAssistantControlBar />
<RoomAudioRenderer />
</LiveKitRoom>
</main>
);
}

function SimpleVoiceAssistant() {
const { state, audioTrack } = useVoiceAssistant();
return (
<div className="h-80">
<BarVisualizer state={state} barCount={7} trackRef={audioTrack} />
</div>
);
}

function onDeviceFailure(error?: MediaDeviceFailure) {
console.error(error);
alert(
"Error acquiring camera or microphone permissions. Please make sure you grant the necessary permissions in your browser and reload the tab"
);
}

0 comments on commit 703e9f7

Please sign in to comment.