Init
This commit is contained in:
6
src/pages/_app.tsx
Normal file
6
src/pages/_app.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
13
src/pages/_document.tsx
Normal file
13
src/pages/_document.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
78
src/pages/api/token.ts
Normal file
78
src/pages/api/token.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
import type { AccessTokenOptions, VideoGrant } from "livekit-server-sdk";
|
||||
import { TokenResult } from "../../lib/types";
|
||||
|
||||
const apiKey = process.env.LIVEKIT_API_KEY;
|
||||
const apiSecret = process.env.LIVEKIT_API_SECRET;
|
||||
|
||||
const createToken = (userInfo: AccessTokenOptions, grant: VideoGrant) => {
|
||||
const at = new AccessToken(apiKey, apiSecret, userInfo);
|
||||
at.addGrant(grant);
|
||||
return at.toJwt();
|
||||
};
|
||||
|
||||
const roomPattern = /\w{4}\-\w{4}/;
|
||||
|
||||
export default async function handleToken(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
const { roomName, identity, name, metadata } = req.query;
|
||||
|
||||
if (typeof identity !== "string" || typeof roomName !== "string") {
|
||||
res.statusMessage =
|
||||
"identity and roomName have to be specified in the request";
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiKey || !apiSecret) {
|
||||
res.statusMessage = "Environment variables aren't set up correctly";
|
||||
res.status(500).end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(name)) {
|
||||
throw Error("provide max one name");
|
||||
}
|
||||
if (Array.isArray(metadata)) {
|
||||
throw Error("provide max one metadata string");
|
||||
}
|
||||
|
||||
// enforce room name to be xxxx-xxxx
|
||||
// this is simple & naive way to prevent user from guessing room names
|
||||
// please use your own authentication mechanisms in your own app
|
||||
if (!roomName.match(roomPattern)) {
|
||||
res.statusMessage = "Invalid roomName";
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!userSession.isAuthenticated) {
|
||||
// res.status(403).end();
|
||||
// return;
|
||||
// }
|
||||
|
||||
const grant: VideoGrant = {
|
||||
room: roomName,
|
||||
roomJoin: true,
|
||||
canPublish: true,
|
||||
canPublishData: true,
|
||||
canSubscribe: true,
|
||||
};
|
||||
|
||||
const token = createToken({ identity, name, metadata }, grant);
|
||||
const result: TokenResult = {
|
||||
identity,
|
||||
accessToken: token,
|
||||
};
|
||||
|
||||
res.status(200).json(result);
|
||||
} catch (e) {
|
||||
res.statusMessage = (e as Error).message;
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
150
src/pages/index.tsx
Normal file
150
src/pages/index.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import Head from "next/head";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Inter } from "next/font/google";
|
||||
import { generateRandomAlphanumeric } from "@/lib/util";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
LiveKitRoom,
|
||||
RoomAudioRenderer,
|
||||
useToken,
|
||||
} from "@livekit/components-react";
|
||||
|
||||
import Playground, {
|
||||
PlaygroundOutputs,
|
||||
} from "@/components/playground/Playground";
|
||||
import { useAppConfig } from "@/hooks/useAppConfig";
|
||||
import { PlaygroundConnect } from "@/components/PlaygroundConnect";
|
||||
import { PlaygroundToast, ToastType } from "@/components/toast/PlaygroundToast";
|
||||
|
||||
const themeColors = [
|
||||
"cyan",
|
||||
"green",
|
||||
"amber",
|
||||
"blue",
|
||||
"violet",
|
||||
"rose",
|
||||
"pink",
|
||||
"teal",
|
||||
];
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export default function Home() {
|
||||
const [toastMessage, setToastMessage] = useState<{
|
||||
message: string;
|
||||
type: ToastType;
|
||||
} | null>(null);
|
||||
const [shouldConnect, setShouldConnect] = useState(false);
|
||||
const [liveKitUrl, setLiveKitUrl] = useState(
|
||||
process.env.NEXT_PUBLIC_LIVEKIT_URL
|
||||
);
|
||||
const [customToken, setCustomToken] = useState<string>();
|
||||
|
||||
const [roomName, setRoomName] = useState(
|
||||
[generateRandomAlphanumeric(4), generateRandomAlphanumeric(4)].join("-")
|
||||
);
|
||||
|
||||
const tokenOptions = useMemo(() => {
|
||||
return {
|
||||
userInfo: { identity: generateRandomAlphanumeric(16) },
|
||||
};
|
||||
}, []);
|
||||
|
||||
// set a new room name each time the user disconnects so that a new token gets fetched behind the scenes for a different room
|
||||
useEffect(() => {
|
||||
if (shouldConnect === false) {
|
||||
setRoomName(
|
||||
[generateRandomAlphanumeric(4), generateRandomAlphanumeric(4)].join("-")
|
||||
);
|
||||
}
|
||||
}, [shouldConnect]);
|
||||
|
||||
const token = useToken("/api/token", roomName, tokenOptions);
|
||||
const appConfig = useAppConfig();
|
||||
const outputs = [
|
||||
appConfig?.outputs.audio && PlaygroundOutputs.Audio,
|
||||
appConfig?.outputs.video && PlaygroundOutputs.Video,
|
||||
appConfig?.outputs.chat && PlaygroundOutputs.Chat,
|
||||
].filter((item) => typeof item !== "boolean") as PlaygroundOutputs[];
|
||||
|
||||
const handleConnect = useCallback(
|
||||
(connect: boolean, opts?: { url: string; token: string }) => {
|
||||
if (connect && opts) {
|
||||
setLiveKitUrl(opts.url);
|
||||
setCustomToken(opts.token);
|
||||
}
|
||||
setShouldConnect(connect);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Agent Playground</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<main className="relative flex flex-col justify-center px-4 items-center h-full w-full bg-black repeating-square-background">
|
||||
<AnimatePresence>
|
||||
{toastMessage && (
|
||||
<motion.div
|
||||
className="left-0 right-0 top-0 absolute z-10"
|
||||
initial={{ opacity: 0, translateY: -50 }}
|
||||
animate={{ opacity: 1, translateY: 0 }}
|
||||
exit={{ opacity: 0, translateY: -50 }}
|
||||
>
|
||||
<PlaygroundToast
|
||||
message={toastMessage.message}
|
||||
type={toastMessage.type}
|
||||
onDismiss={() => {
|
||||
setToastMessage(null);
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{liveKitUrl ? (
|
||||
<LiveKitRoom
|
||||
className="flex flex-col h-full w-full"
|
||||
serverUrl={liveKitUrl}
|
||||
token={customToken ?? token}
|
||||
audio={appConfig?.inputs.mic}
|
||||
video={appConfig?.inputs.camera}
|
||||
connect={shouldConnect}
|
||||
onError={(e) => {
|
||||
setToastMessage({ message: e.message, type: "error" });
|
||||
console.error(e);
|
||||
}}
|
||||
>
|
||||
<Playground
|
||||
title={appConfig?.title}
|
||||
githubLink={appConfig?.github_link}
|
||||
outputs={outputs}
|
||||
showQR={appConfig?.show_qr}
|
||||
description={appConfig?.description}
|
||||
themeColors={themeColors}
|
||||
defaultColor={appConfig?.theme_color ?? "cyan"}
|
||||
onConnect={handleConnect}
|
||||
metadata={[{ name: "Room Name", value: roomName }]}
|
||||
/>
|
||||
<RoomAudioRenderer />
|
||||
</LiveKitRoom>
|
||||
) : (
|
||||
<PlaygroundConnect
|
||||
accentColor={themeColors[0]}
|
||||
onConnectClicked={(url, token) => {
|
||||
handleConnect(true, { url, token });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user