Introduce ability to disable editing the UI (useful for hosted demos) (#48)

This commit is contained in:
Neil Dwyer 2024-05-06 12:13:45 -07:00 committed by GitHub
parent 5b99dae15f
commit 95387d73bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 39 additions and 21 deletions

View File

@ -12,6 +12,7 @@ description: 'LiveKit Agent Playground allows you to test your LiveKit Agent int
github_link: 'https://github.com/livekit/agents-playground' github_link: 'https://github.com/livekit/agents-playground'
video_fit: 'cover' # 'contain' or 'cover' video_fit: 'cover' # 'contain' or 'cover'
settings: settings:
editable: true # Should the user be able to edit settings in-app
theme_color: 'cyan' theme_color: 'cyan'
chat: true # Enable or disable chat feature chat: true # Enable or disable chat feature
outputs: outputs:

View File

@ -36,12 +36,6 @@ import {
import { QRCodeSVG } from "qrcode.react"; import { QRCodeSVG } from "qrcode.react";
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react"; import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
export enum PlaygroundOutputs {
Video,
Audio,
Chat,
}
export interface PlaygroundMeta { export interface PlaygroundMeta {
name: string; name: string;
value: string; value: string;
@ -66,7 +60,6 @@ export default function Playground({
const [messages, setMessages] = useState<ChatMessageType[]>([]); const [messages, setMessages] = useState<ChatMessageType[]>([]);
const [transcripts, setTranscripts] = useState<ChatMessageType[]>([]); const [transcripts, setTranscripts] = useState<ChatMessageType[]>([]);
const { localParticipant } = useLocalParticipant(); const { localParticipant } = useLocalParticipant();
const [outputs, setOutputs] = useState<PlaygroundOutputs[]>([]);
const participants = useRemoteParticipants({ const participants = useRemoteParticipants({
updateOnlyOn: [RoomEvent.ParticipantMetadataChanged], updateOnlyOn: [RoomEvent.ParticipantMetadataChanged],
@ -371,7 +364,8 @@ export default function Playground({
config.description, config.description,
config.settings, config.settings,
config.show_qr, config.show_qr,
// metadata, localParticipant,
name,
roomState, roomState,
isAgentConnected, isAgentConnected,
agentState, agentState,
@ -383,7 +377,7 @@ export default function Playground({
]); ]);
let mobileTabs: PlaygroundTab[] = []; let mobileTabs: PlaygroundTab[] = [];
if (outputs?.includes(PlaygroundOutputs.Video)) { if (config.settings.outputs.video) {
mobileTabs.push({ mobileTabs.push({
title: "Video", title: "Video",
content: ( content: (
@ -397,7 +391,7 @@ export default function Playground({
}); });
} }
if (outputs?.includes(PlaygroundOutputs.Audio)) { if (config.settings.outputs.audio) {
mobileTabs.push({ mobileTabs.push({
title: "Audio", title: "Audio",
content: ( content: (
@ -411,7 +405,7 @@ export default function Playground({
}); });
} }
if (outputs?.includes(PlaygroundOutputs.Chat)) { if (config.settings.chat) {
mobileTabs.push({ mobileTabs.push({
title: "Chat", title: "Chat",
content: chatTileContent, content: chatTileContent,
@ -458,13 +452,12 @@ export default function Playground({
</div> </div>
<div <div
className={`flex-col grow basis-1/2 gap-4 h-full hidden lg:${ className={`flex-col grow basis-1/2 gap-4 h-full hidden lg:${
!outputs?.includes(PlaygroundOutputs.Audio) && !config.settings.outputs.audio && !config.settings.outputs.video
!outputs?.includes(PlaygroundOutputs.Video)
? "hidden" ? "hidden"
: "flex" : "flex"
}`} }`}
> >
{outputs?.includes(PlaygroundOutputs.Video) && ( {config.settings.outputs.video && (
<PlaygroundTile <PlaygroundTile
title="Video" title="Video"
className="w-full h-full grow" className="w-full h-full grow"
@ -473,7 +466,7 @@ export default function Playground({
{videoTileContent} {videoTileContent}
</PlaygroundTile> </PlaygroundTile>
)} )}
{outputs?.includes(PlaygroundOutputs.Audio) && ( {config.settings.outputs.audio && (
<PlaygroundTile <PlaygroundTile
title="Audio" title="Audio"
className="w-full h-full grow" className="w-full h-full grow"
@ -484,7 +477,7 @@ export default function Playground({
)} )}
</div> </div>
{outputs?.includes(PlaygroundOutputs.Chat) && ( {config.settings.chat && (
<PlaygroundTile <PlaygroundTile
title="Chat" title="Chat"
className="h-full grow basis-1/4 hidden lg:flex" className="h-full grow basis-1/4 hidden lg:flex"

View File

@ -1,6 +1,7 @@
import { Button } from "@/components/button/Button"; import { Button } from "@/components/button/Button";
import { LoadingSVG } from "@/components/button/LoadingSVG"; import { LoadingSVG } from "@/components/button/LoadingSVG";
import { SettingsDropdown } from "@/components/playground/SettingsDropdown"; import { SettingsDropdown } from "@/components/playground/SettingsDropdown";
import { useConfig } from "@/hooks/useConfig";
import { ConnectionState } from "livekit-client"; import { ConnectionState } from "livekit-client";
import { ReactNode } from "react"; import { ReactNode } from "react";
@ -23,6 +24,7 @@ export const PlaygroundHeader = ({
onConnectClicked, onConnectClicked,
connectionState, connectionState,
}: PlaygroundHeader) => { }: PlaygroundHeader) => {
const { config } = useConfig();
return ( return (
<div <div
className={`flex gap-4 pt-4 text-${accentColor}-500 justify-between items-center shrink-0`} className={`flex gap-4 pt-4 text-${accentColor}-500 justify-between items-center shrink-0`}
@ -48,7 +50,7 @@ export const PlaygroundHeader = ({
<GithubSVG /> <GithubSVG />
</a> </a>
)} )}
<SettingsDropdown /> {config.settings.editable && <SettingsDropdown />}
<Button <Button
accentColor={ accentColor={
connectionState === ConnectionState.Connected ? "red" : accentColor connectionState === ConnectionState.Connected ? "red" : accentColor

View File

@ -66,6 +66,9 @@ export const PlaygroundTabbedTile: React.FC<PlaygroundTabbedTileProps> = ({
}) => { }) => {
const contentPadding = 4; const contentPadding = 4;
const [activeTab, setActiveTab] = useState(initialTab); const [activeTab, setActiveTab] = useState(initialTab);
if(activeTab >= tabs.length) {
return null;
}
return ( return (
<div <div
className={`flex flex-col h-full border rounded-sm border-gray-800 text-gray-500 bg-${backgroundColor} ${className}`} className={`flex flex-col h-full border rounded-sm border-gray-800 text-gray-500 bg-${backgroundColor} ${className}`}

View File

@ -16,6 +16,7 @@ export type AppConfig = {
}; };
export type UserSettings = { export type UserSettings = {
editable: boolean;
theme_color: string; theme_color: string;
chat: boolean; chat: boolean;
inputs: { inputs: {
@ -36,6 +37,7 @@ const defaultConfig: AppConfig = {
description: "A playground for testing LiveKit Agents", description: "A playground for testing LiveKit Agents",
video_fit: "cover", video_fit: "cover",
settings: { settings: {
editable: true,
theme_color: "cyan", theme_color: "cyan",
chat: true, chat: true,
inputs: { inputs: {
@ -90,8 +92,13 @@ export const ConfigProvider = ({
if (!window.location.hash) { if (!window.location.hash) {
return null; return null;
} }
const appConfigFromSettings = appConfig;
if (appConfigFromSettings.settings.editable === false) {
return null
}
const params = new URLSearchParams(window.location.hash.replace("#", "")); const params = new URLSearchParams(window.location.hash.replace("#", ""));
return { return {
editable: true,
chat: params.get("chat") === "1", chat: params.get("chat") === "1",
theme_color: params.get("theme_color"), theme_color: params.get("theme_color"),
inputs: { inputs: {
@ -106,15 +113,19 @@ export const ConfigProvider = ({
ws_url: "", ws_url: "",
token: "" token: ""
} as UserSettings; } as UserSettings;
}, []) }, [appConfig])
const getSettingsFromCookies = useCallback(() => { const getSettingsFromCookies = useCallback(() => {
const appConfigFromSettings = appConfig;
if (appConfigFromSettings.settings.editable === false) {
return null
}
const jsonSettings = getCookie("lk_settings"); const jsonSettings = getCookie("lk_settings");
if (!jsonSettings) { if (!jsonSettings) {
return null; return null;
} }
return JSON.parse(jsonSettings) as UserSettings; return JSON.parse(jsonSettings) as UserSettings;
}, []) }, [appConfig])
const setUrlSettings = useCallback((us: UserSettings) => { const setUrlSettings = useCallback((us: UserSettings) => {
const obj = new URLSearchParams({ const obj = new URLSearchParams({
@ -136,6 +147,9 @@ export const ConfigProvider = ({
const getConfig = useCallback(() => { const getConfig = useCallback(() => {
const appConfigFromSettings = appConfig; const appConfigFromSettings = appConfig;
if (appConfigFromSettings.settings.editable === false) {
return appConfigFromSettings;
}
const cookieSettigs = getSettingsFromCookies(); const cookieSettigs = getSettingsFromCookies();
const urlSettings = getSettingsFromUrl(); const urlSettings = getSettingsFromUrl();
if(!cookieSettigs) { if(!cookieSettigs) {
@ -163,6 +177,10 @@ export const ConfigProvider = ({
]); ]);
const setUserSettings = useCallback((settings: UserSettings) => { const setUserSettings = useCallback((settings: UserSettings) => {
const appConfigFromSettings = appConfig;
if (appConfigFromSettings.settings.editable === false) {
return
}
setUrlSettings(settings); setUrlSettings(settings);
setCookieSettings(settings); setCookieSettings(settings);
_setConfig((prev) => { _setConfig((prev) => {
@ -171,7 +189,7 @@ export const ConfigProvider = ({
settings: settings, settings: settings,
}; };
}) })
}, [setCookieSettings, setUrlSettings]); }, [appConfig, setCookieSettings, setUrlSettings]);
const [config, _setConfig] = useState<AppConfig>(getConfig()); const [config, _setConfig] = useState<AppConfig>(getConfig());

View File

@ -31,6 +31,7 @@ export const TokenGeneratorProvider = ({
const [shouldConnect, setShouldConnect] = useState(false); const [shouldConnect, setShouldConnect] = useState(false);
const connect = useCallback( const connect = useCallback(
async (mode: Mode) => { async (mode: Mode) => {
console.log("connecting", mode);
if (mode === "cloud") { if (mode === "cloud") {
if (!generateConnectionDetails) { if (!generateConnectionDetails) {
throw new Error( throw new Error(