configurable room name and perform RPC calls (#127)
This commit is contained in:
parent
2082deb0e3
commit
201a13cc78
@ -20,3 +20,43 @@ export const NameValueRow: React.FC<NameValueRowProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type EditableNameValueRowProps = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
valueColor?: string;
|
||||||
|
onValueChange?: (value: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
editable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditableNameValueRow: React.FC<EditableNameValueRowProps> = ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
valueColor = "gray-300",
|
||||||
|
onValueChange,
|
||||||
|
placeholder,
|
||||||
|
editable,
|
||||||
|
}) => {
|
||||||
|
if (editable && onValueChange) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row w-full items-baseline text-sm">
|
||||||
|
<div className="grow shrink-0 text-gray-500">{name}</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onValueChange(e.target.value)}
|
||||||
|
className={`text-xs shrink text-${valueColor} text-right bg-transparent border-b border-gray-800 focus:outline-none focus:border-gray-600 px-2 py-0`}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<NameValueRow
|
||||||
|
name={name}
|
||||||
|
value={value}
|
||||||
|
valueColor={valueColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -23,11 +23,13 @@ import {
|
|||||||
useRoomInfo,
|
useRoomInfo,
|
||||||
useTracks,
|
useTracks,
|
||||||
useVoiceAssistant,
|
useVoiceAssistant,
|
||||||
|
useRoomContext,
|
||||||
} from "@livekit/components-react";
|
} from "@livekit/components-react";
|
||||||
import { ConnectionState, LocalParticipant, Track } from "livekit-client";
|
import { ConnectionState, LocalParticipant, Track } from "livekit-client";
|
||||||
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";
|
||||||
import tailwindTheme from "../../lib/tailwindTheme.preval";
|
import tailwindTheme from "../../lib/tailwindTheme.preval";
|
||||||
|
import { EditableNameValueRow } from "@/components/config/NameValueRow";
|
||||||
|
|
||||||
export interface PlaygroundMeta {
|
export interface PlaygroundMeta {
|
||||||
name: string;
|
name: string;
|
||||||
@ -56,6 +58,10 @@ export default function Playground({
|
|||||||
|
|
||||||
const roomState = useConnectionState();
|
const roomState = useConnectionState();
|
||||||
const tracks = useTracks();
|
const tracks = useTracks();
|
||||||
|
const room = useRoomContext();
|
||||||
|
|
||||||
|
const [rpcMethod, setRpcMethod] = useState("");
|
||||||
|
const [rpcPayload, setRpcPayload] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (roomState === ConnectionState.Connected) {
|
if (roomState === ConnectionState.Connected) {
|
||||||
@ -212,6 +218,21 @@ export default function Playground({
|
|||||||
return <></>;
|
return <></>;
|
||||||
}, [config.settings.theme_color, voiceAssistant.audioTrack]);
|
}, [config.settings.theme_color, voiceAssistant.audioTrack]);
|
||||||
|
|
||||||
|
const handleRpcCall = useCallback(async () => {
|
||||||
|
if (!voiceAssistant.agent || !room) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await room.localParticipant.performRpc({
|
||||||
|
destinationIdentity: voiceAssistant.agent.identity,
|
||||||
|
method: rpcMethod,
|
||||||
|
payload: rpcPayload,
|
||||||
|
});
|
||||||
|
console.log('RPC response:', response);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('RPC call failed:', e);
|
||||||
|
}
|
||||||
|
}, [room, rpcMethod, rpcPayload, voiceAssistant.agent]);
|
||||||
|
|
||||||
const settingsTileContent = useMemo(() => {
|
const settingsTileContent = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 h-full w-full items-start overflow-y-auto">
|
<div className="flex flex-col gap-4 h-full w-full items-start overflow-y-auto">
|
||||||
@ -222,19 +243,65 @@ export default function Playground({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<ConfigurationPanelItem title="Settings">
|
<ConfigurationPanelItem title="Settings">
|
||||||
{localParticipant && (
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-2">
|
<EditableNameValueRow
|
||||||
<NameValueRow
|
|
||||||
name="Room"
|
name="Room"
|
||||||
value={name}
|
value={roomState === ConnectionState.Connected ? name : config.settings.room_name}
|
||||||
valueColor={`${config.settings.theme_color}-500`}
|
valueColor={`${config.settings.theme_color}-500`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const newSettings = { ...config.settings };
|
||||||
|
newSettings.room_name = value;
|
||||||
|
setUserSettings(newSettings);
|
||||||
|
}}
|
||||||
|
placeholder="Enter room name"
|
||||||
|
editable={roomState !== ConnectionState.Connected}
|
||||||
/>
|
/>
|
||||||
<NameValueRow
|
<EditableNameValueRow
|
||||||
name="Participant"
|
name="Participant"
|
||||||
value={localParticipant.identity}
|
value={roomState === ConnectionState.Connected ?
|
||||||
|
(localParticipant?.identity || '') :
|
||||||
|
(config.settings.participant_name || '')}
|
||||||
|
valueColor={`${config.settings.theme_color}-500`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const newSettings = { ...config.settings };
|
||||||
|
newSettings.participant_name = value;
|
||||||
|
setUserSettings(newSettings);
|
||||||
|
}}
|
||||||
|
placeholder="Enter participant id"
|
||||||
|
editable={roomState !== ConnectionState.Connected}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex flex-col gap-2 mt-4">
|
||||||
|
<div className="text-xs text-gray-500 mt-2">RPC Method</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={rpcMethod}
|
||||||
|
onChange={(e) => setRpcMethod(e.target.value)}
|
||||||
|
className="w-full text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
|
||||||
|
placeholder="RPC method name"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="text-xs text-gray-500 mt-2">RPC Payload</div>
|
||||||
|
<textarea
|
||||||
|
value={rpcPayload}
|
||||||
|
onChange={(e) => setRpcPayload(e.target.value)}
|
||||||
|
className="w-full text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
|
||||||
|
placeholder="RPC payload"
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleRpcCall}
|
||||||
|
disabled={!voiceAssistant.agent || !rpcMethod}
|
||||||
|
className={`mt-2 px-2 py-1 rounded-sm text-xs
|
||||||
|
${voiceAssistant.agent && rpcMethod
|
||||||
|
? `bg-${config.settings.theme_color}-500 hover:bg-${config.settings.theme_color}-600`
|
||||||
|
: 'bg-gray-700 cursor-not-allowed'
|
||||||
|
} text-white`}
|
||||||
|
>
|
||||||
|
Perform RPC Call
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</ConfigurationPanelItem>
|
</ConfigurationPanelItem>
|
||||||
<ConfigurationPanelItem title="Status">
|
<ConfigurationPanelItem title="Status">
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@ -327,6 +394,9 @@ export default function Playground({
|
|||||||
themeColors,
|
themeColors,
|
||||||
setUserSettings,
|
setUserSettings,
|
||||||
voiceAssistant.agent,
|
voiceAssistant.agent,
|
||||||
|
rpcMethod,
|
||||||
|
rpcPayload,
|
||||||
|
handleRpcCall,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mobileTabs: PlaygroundTab[] = [];
|
let mobileTabs: PlaygroundTab[] = [];
|
||||||
|
|||||||
@ -34,6 +34,8 @@ export type UserSettings = {
|
|||||||
};
|
};
|
||||||
ws_url: string;
|
ws_url: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
room_name: string;
|
||||||
|
participant_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fallback if NEXT_PUBLIC_APP_CONFIG is not set
|
// Fallback if NEXT_PUBLIC_APP_CONFIG is not set
|
||||||
@ -55,6 +57,8 @@ const defaultConfig: AppConfig = {
|
|||||||
},
|
},
|
||||||
ws_url: "",
|
ws_url: "",
|
||||||
token: "",
|
token: "",
|
||||||
|
room_name: "",
|
||||||
|
participant_name: "",
|
||||||
},
|
},
|
||||||
show_qr: false,
|
show_qr: false,
|
||||||
};
|
};
|
||||||
@ -122,6 +126,8 @@ export const ConfigProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
},
|
},
|
||||||
ws_url: "",
|
ws_url: "",
|
||||||
token: "",
|
token: "",
|
||||||
|
room_name: "",
|
||||||
|
participant_name: "",
|
||||||
} as UserSettings;
|
} as UserSettings;
|
||||||
}, [appConfig]);
|
}, [appConfig]);
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,14 @@ export const ConnectionProvider = ({
|
|||||||
throw new Error("NEXT_PUBLIC_LIVEKIT_URL is not set");
|
throw new Error("NEXT_PUBLIC_LIVEKIT_URL is not set");
|
||||||
}
|
}
|
||||||
url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
|
url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
|
||||||
const { accessToken } = await fetch("/api/token").then((res) =>
|
const params = new URLSearchParams();
|
||||||
|
if (config.settings.room_name) {
|
||||||
|
params.append('roomName', config.settings.room_name);
|
||||||
|
}
|
||||||
|
if (config.settings.participant_name) {
|
||||||
|
params.append('participantName', config.settings.participant_name);
|
||||||
|
}
|
||||||
|
const { accessToken } = await fetch(`/api/token?${params}`).then((res) =>
|
||||||
res.json()
|
res.json()
|
||||||
);
|
);
|
||||||
token = accessToken;
|
token = accessToken;
|
||||||
@ -68,6 +75,8 @@ export const ConnectionProvider = ({
|
|||||||
cloudWSUrl,
|
cloudWSUrl,
|
||||||
config.settings.token,
|
config.settings.token,
|
||||||
config.settings.ws_url,
|
config.settings.ws_url,
|
||||||
|
config.settings.room_name,
|
||||||
|
config.settings.participant_name,
|
||||||
generateToken,
|
generateToken,
|
||||||
setToastMessage,
|
setToastMessage,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -25,8 +25,13 @@ export default async function handleToken(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomName = `room-${generateRandomAlphanumeric(4)}-${generateRandomAlphanumeric(4)}`;
|
// Get room name from query params or generate random one
|
||||||
const identity = `identity-${generateRandomAlphanumeric(4)}`
|
const roomName = req.query.roomName as string ||
|
||||||
|
`room-${generateRandomAlphanumeric(4)}-${generateRandomAlphanumeric(4)}`;
|
||||||
|
|
||||||
|
// Get participant name from query params or generate random one
|
||||||
|
const identity = req.query.participantName as string ||
|
||||||
|
`identity-${generateRandomAlphanumeric(4)}`;
|
||||||
|
|
||||||
const grant: VideoGrant = {
|
const grant: VideoGrant = {
|
||||||
room: roomName,
|
room: roomName,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user