Skip to content

Commit 201a13c

Browse files
authored
configurable room name and perform RPC calls (#127)
1 parent 2082deb commit 201a13c

File tree

5 files changed

+146
-16
lines changed

5 files changed

+146
-16
lines changed

src/components/config/NameValueRow.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,43 @@ export const NameValueRow: React.FC<NameValueRowProps> = ({
2020
</div>
2121
);
2222
};
23+
24+
type EditableNameValueRowProps = {
25+
name: string;
26+
value: string;
27+
valueColor?: string;
28+
onValueChange?: (value: string) => void;
29+
placeholder?: string;
30+
editable: boolean;
31+
};
32+
33+
export const EditableNameValueRow: React.FC<EditableNameValueRowProps> = ({
34+
name,
35+
value,
36+
valueColor = "gray-300",
37+
onValueChange,
38+
placeholder,
39+
editable,
40+
}) => {
41+
if (editable && onValueChange) {
42+
return (
43+
<div className="flex flex-row w-full items-baseline text-sm">
44+
<div className="grow shrink-0 text-gray-500">{name}</div>
45+
<input
46+
type="text"
47+
value={value}
48+
onChange={(e) => onValueChange(e.target.value)}
49+
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`}
50+
placeholder={placeholder}
51+
/>
52+
</div>
53+
);
54+
}
55+
return (
56+
<NameValueRow
57+
name={name}
58+
value={value}
59+
valueColor={valueColor}
60+
/>
61+
);
62+
};

src/components/playground/Playground.tsx

+83-13
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import {
2323
useRoomInfo,
2424
useTracks,
2525
useVoiceAssistant,
26+
useRoomContext,
2627
} from "@livekit/components-react";
2728
import { ConnectionState, LocalParticipant, Track } from "livekit-client";
2829
import { QRCodeSVG } from "qrcode.react";
2930
import { ReactNode, useCallback, useEffect, useMemo, useState } from "react";
3031
import tailwindTheme from "../../lib/tailwindTheme.preval";
32+
import { EditableNameValueRow } from "@/components/config/NameValueRow";
3133

3234
export interface PlaygroundMeta {
3335
name: string;
@@ -56,6 +58,10 @@ export default function Playground({
5658

5759
const roomState = useConnectionState();
5860
const tracks = useTracks();
61+
const room = useRoomContext();
62+
63+
const [rpcMethod, setRpcMethod] = useState("");
64+
const [rpcPayload, setRpcPayload] = useState("");
5965

6066
useEffect(() => {
6167
if (roomState === ConnectionState.Connected) {
@@ -212,6 +218,21 @@ export default function Playground({
212218
return <></>;
213219
}, [config.settings.theme_color, voiceAssistant.audioTrack]);
214220

221+
const handleRpcCall = useCallback(async () => {
222+
if (!voiceAssistant.agent || !room) return;
223+
224+
try {
225+
const response = await room.localParticipant.performRpc({
226+
destinationIdentity: voiceAssistant.agent.identity,
227+
method: rpcMethod,
228+
payload: rpcPayload,
229+
});
230+
console.log('RPC response:', response);
231+
} catch (e) {
232+
console.error('RPC call failed:', e);
233+
}
234+
}, [room, rpcMethod, rpcPayload, voiceAssistant.agent]);
235+
215236
const settingsTileContent = useMemo(() => {
216237
return (
217238
<div className="flex flex-col gap-4 h-full w-full items-start overflow-y-auto">
@@ -222,19 +243,65 @@ export default function Playground({
222243
)}
223244

224245
<ConfigurationPanelItem title="Settings">
225-
{localParticipant && (
226-
<div className="flex flex-col gap-2">
227-
<NameValueRow
228-
name="Room"
229-
value={name}
230-
valueColor={`${config.settings.theme_color}-500`}
231-
/>
232-
<NameValueRow
233-
name="Participant"
234-
value={localParticipant.identity}
235-
/>
236-
</div>
237-
)}
246+
<div className="flex flex-col gap-4">
247+
<EditableNameValueRow
248+
name="Room"
249+
value={roomState === ConnectionState.Connected ? name : config.settings.room_name}
250+
valueColor={`${config.settings.theme_color}-500`}
251+
onValueChange={(value) => {
252+
const newSettings = { ...config.settings };
253+
newSettings.room_name = value;
254+
setUserSettings(newSettings);
255+
}}
256+
placeholder="Enter room name"
257+
editable={roomState !== ConnectionState.Connected}
258+
/>
259+
<EditableNameValueRow
260+
name="Participant"
261+
value={roomState === ConnectionState.Connected ?
262+
(localParticipant?.identity || '') :
263+
(config.settings.participant_name || '')}
264+
valueColor={`${config.settings.theme_color}-500`}
265+
onValueChange={(value) => {
266+
const newSettings = { ...config.settings };
267+
newSettings.participant_name = value;
268+
setUserSettings(newSettings);
269+
}}
270+
placeholder="Enter participant id"
271+
editable={roomState !== ConnectionState.Connected}
272+
/>
273+
</div>
274+
<div className="flex flex-col gap-2 mt-4">
275+
<div className="text-xs text-gray-500 mt-2">RPC Method</div>
276+
<input
277+
type="text"
278+
value={rpcMethod}
279+
onChange={(e) => setRpcMethod(e.target.value)}
280+
className="w-full text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
281+
placeholder="RPC method name"
282+
/>
283+
284+
<div className="text-xs text-gray-500 mt-2">RPC Payload</div>
285+
<textarea
286+
value={rpcPayload}
287+
onChange={(e) => setRpcPayload(e.target.value)}
288+
className="w-full text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
289+
placeholder="RPC payload"
290+
rows={2}
291+
/>
292+
293+
<button
294+
onClick={handleRpcCall}
295+
disabled={!voiceAssistant.agent || !rpcMethod}
296+
className={`mt-2 px-2 py-1 rounded-sm text-xs
297+
${voiceAssistant.agent && rpcMethod
298+
? `bg-${config.settings.theme_color}-500 hover:bg-${config.settings.theme_color}-600`
299+
: 'bg-gray-700 cursor-not-allowed'
300+
} text-white`}
301+
>
302+
Perform RPC Call
303+
</button>
304+
</div>
238305
</ConfigurationPanelItem>
239306
<ConfigurationPanelItem title="Status">
240307
<div className="flex flex-col gap-2">
@@ -327,6 +394,9 @@ export default function Playground({
327394
themeColors,
328395
setUserSettings,
329396
voiceAssistant.agent,
397+
rpcMethod,
398+
rpcPayload,
399+
handleRpcCall,
330400
]);
331401

332402
let mobileTabs: PlaygroundTab[] = [];

src/hooks/useConfig.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export type UserSettings = {
3434
};
3535
ws_url: string;
3636
token: string;
37+
room_name: string;
38+
participant_name: string;
3739
};
3840

3941
// Fallback if NEXT_PUBLIC_APP_CONFIG is not set
@@ -55,6 +57,8 @@ const defaultConfig: AppConfig = {
5557
},
5658
ws_url: "",
5759
token: "",
60+
room_name: "",
61+
participant_name: "",
5862
},
5963
show_qr: false,
6064
};
@@ -122,6 +126,8 @@ export const ConfigProvider = ({ children }: { children: React.ReactNode }) => {
122126
},
123127
ws_url: "",
124128
token: "",
129+
room_name: "",
130+
participant_name: "",
125131
} as UserSettings;
126132
}, [appConfig]);
127133

src/hooks/useConnection.tsx

+10-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ export const ConnectionProvider = ({
5454
throw new Error("NEXT_PUBLIC_LIVEKIT_URL is not set");
5555
}
5656
url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
57-
const { accessToken } = await fetch("/api/token").then((res) =>
57+
const params = new URLSearchParams();
58+
if (config.settings.room_name) {
59+
params.append('roomName', config.settings.room_name);
60+
}
61+
if (config.settings.participant_name) {
62+
params.append('participantName', config.settings.participant_name);
63+
}
64+
const { accessToken } = await fetch(`/api/token?${params}`).then((res) =>
5865
res.json()
5966
);
6067
token = accessToken;
@@ -68,6 +75,8 @@ export const ConnectionProvider = ({
6875
cloudWSUrl,
6976
config.settings.token,
7077
config.settings.ws_url,
78+
config.settings.room_name,
79+
config.settings.participant_name,
7180
generateToken,
7281
setToastMessage,
7382
]

src/pages/api/token.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ export default async function handleToken(
2525
return;
2626
}
2727

28-
const roomName = `room-${generateRandomAlphanumeric(4)}-${generateRandomAlphanumeric(4)}`;
29-
const identity = `identity-${generateRandomAlphanumeric(4)}`
28+
// Get room name from query params or generate random one
29+
const roomName = req.query.roomName as string ||
30+
`room-${generateRandomAlphanumeric(4)}-${generateRandomAlphanumeric(4)}`;
31+
32+
// Get participant name from query params or generate random one
33+
const identity = req.query.participantName as string ||
34+
`identity-${generateRandomAlphanumeric(4)}`;
3035

3136
const grant: VideoGrant = {
3237
room: roomName,

0 commit comments

Comments
 (0)