The room diagnostic console we have all been wanting.
Key Features:
- Real-time logs of all events
- Full participant/room/tracks state visibility
- Ability to invoke server-side actions in real time
- remove participants
- update attributes
- modify metadata
- mute/unmute tracks
- sending data packet with topic
- and more...
- (WIP) Monitor and invoke RPC calls on the client/server (agent) side
How To Use
Hosted here
- first run - click gear top right of screen
- Set your LiveKit credentials and save
- Now enter
Room Name
andObserver ID
and click Start Observing - Click Connect
- Run the
agent-console
frontend locally.
cd agent-console
npm i
npm run dev
- Install
uv
package manager and run the pipeline agent locally.
pip install uv
- Run the pipeline agent locally.
cd agent-examples
uv sync
make run-pipeline-agent
The codebase of the frontend is been made to encourage extensibility and customization for individual's own purposes.
If you are intended to build certain custom features that want the observable state of your own livekit application, here's the recommended approach:
Currently, the app has 4 main views:
- Room - Monitor and manage the room state
- Local Participant - Monitor and manage the local participant state and tracks
- Remote Participants - Monitor and manage the remote participants' state and tracks
- Videos - View the realtime stream of the videos
To extend the app with new views, you can do the following:
- Inside
app/_components/livekit-state-tabs.tsx
file, add a new tab with the following code:
<LivekitStateContent value="new-view">
<div>New View</div>
</LivekitStateContent>
-
Make a new hook (or use existing ones) inside
hooks
folder, the hook should return the data that you want to display in the new view. -
Use the prebuilt components
ObservableWrapper
insidecomponents/observable-wrapper.tsx
to display the data in the new view, and add as a child of theLivekitStateContent
component. TheObservableWrapper
component is a helper compoent that already handles displaying UI of the state as well as the underling state JSON data, and also has a toggle button to toggle between two views (JSON view and UI view).
So you should end up having something like this:
const state = useMyCustomDataHook();
<ObservableWrapper title="View's Title" subtitle="View Subtitle" state={state}>
{(state) => <MuCustomViewer {...state} />}
</ObservableWrapper>;
All the current log definitions are defined in lib/event-definitions.ts
file. Each log event definition has the following types (defined in lib/event-registry.ts
):
export interface EventDefinition<TData extends object> {
level: WithCallable<TData, EventLevel>;
source: WithCallable<TData, EventSource>;
message: WithCallable<TData, string>;
render: WithCallable<TData, React.ReactNode>;
}
For example, the participantConnected
event definition is defined as follows:
export const eventRegistryConfig = {
..., // other event definitions
participantConnected: defineEvent<{ participant: RemoteParticipant }>({
level: EventLevel.Info,
source: EventSource.Server,
message: ({ participant }) =>
isAgent(participant)
? `An agent "${participant.identity}" has joined the room`
: `A new remote participant "${participant.identity}" has joined the room`,
render: ({ participant }) => renderJson({ participant }),
}),
..., // other event definitions
}
Right now the detailed view of each event log is defaulted to renderJson
function, which is a helper function to display the JSON data in a pretty way. You can customize the detailed view by overriding the render
property to a custom component that you want to display.
You can also extend the set of event definitions by adding new ones in the eventRegistryConfig
object in the event-definitions.ts
file.
Right now all the livekit logs are automatically instrumented by the LivekitEventInstrumentor
component inside providers/livekit-event-instrumentor.tsx
. You can alwasy use the useLogger
hook inside hooks/use-logger.ts
to manually log new events to the console. For example, say you defined a new event type myCustomEvent
in the eventRegistryConfig:
export const eventRegistryConfig = {
..., // other event definitions
myCustomEvent: defineEvent<{ customData: string }>({
level: ({ customData }) => (customData === "error" ? EventLevel.Error : EventLevel.Info),
source: EventSource.Client,
message: ({ customData }) => `My custom event: ${customData}`,
render: ({ customData }) => <div>My custom event: {customData}</div>,
}),
};
You can then log a new event to the console by calling the useLogger
hook:
const { appendLog } = useLogger();
appendLog("myCustomEvent", { customData: "error" });
The object type schema of the data is automatically inferred by the event name.