openvidu-testapp: added controls for Agent Dispatch API

pull/904/head
pabloFuente 2026-06-23 22:52:59 +02:00
parent fdd0441e8e
commit 9d4b11ed58
4 changed files with 199 additions and 1 deletions

View File

@ -127,6 +127,14 @@ mat-chip-row {
margin-bottom: 7px; margin-bottom: 7px;
} }
.agent-dispatch-options {
margin-bottom: 7px;
}
#agent-restart-policy-select {
margin-left: 6px;
}
#ingress-url-type-select { #ingress-url-type-select {
margin-left: 6px; margin-left: 6px;
} }

View File

@ -256,6 +256,68 @@
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div>
<label class="label">Agent Dispatch API</label>
<mat-form-field class="inner-text-input" [style.fontSize.px]=14>
<mat-label>Room</mat-label>
<input matInput id="agent-room-name-field" placeholder="Room" [(ngModel)]="agentRoomName">
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]=14>
<mat-label>Agent name</mat-label>
<input matInput id="agent-name-field" placeholder="Agent name" [(ngModel)]="agentName">
</mat-form-field>
<mat-form-field class="inner-text-input" [style.fontSize.px]=14>
<mat-label>Dispatch ID</mat-label>
<input matInput id="agent-dispatch-id-field" placeholder="Dispatch ID" [(ngModel)]="agentDispatchId">
</mat-form-field>
</div>
<div class="button-group">
<span style="display: inline-block" [matTooltip]="!agentRoomName ? 'Room required' : 'Agent name required'"
[matTooltipDisabled]="!!agentRoomName && !!agentName">
<button mat-button id="create-dispatch-api-btn" (click)="createDispatch()"
[disabled]="!agentRoomName || !agentName">Create Dispatch</button>
</span>
<span style="display: inline-block" matTooltip='"Room" required' [matTooltipDisabled]="!!agentRoomName">
<button mat-button id="list-dispatch-api-btn" (click)="listDispatch()" [disabled]="!agentRoomName">List
Dispatch</button>
</span>
<span style="display: inline-block" matTooltip='"Room" and "Dispatch ID" required'
[matTooltipDisabled]="!!agentRoomName && !!agentDispatchId">
<button mat-button id="get-dispatch-api-btn" (click)="getDispatch()"
[disabled]="!agentRoomName || !agentDispatchId">Get Dispatch</button>
</span>
<span style="display: inline-block" matTooltip='"Room" and "Dispatch ID" required'
[matTooltipDisabled]="!!agentRoomName && !!agentDispatchId">
<button mat-button id="delete-dispatch-api-btn" (click)="deleteDispatch()"
[disabled]="!agentRoomName || !agentDispatchId">Delete Dispatch</button>
</span>
<span style="display: inline-block" matTooltip='"Room" required' [matTooltipDisabled]="!!agentRoomName">
<button mat-button id="delete-all-dispatch-api-btn" (click)="deleteAllDispatch()" [disabled]="!agentRoomName"
style="font-style: italic; font-size: 0.75rem; margin-left: 5px;">Delete all</button>
</span>
</div>
<label class="label">Dispatch options</label>
<div class="agent-dispatch-options">
<mat-form-field class="inner-text-input" [style.fontSize.px]=14>
<mat-label>Metadata</mat-label>
<input matInput id="agent-metadata-field" placeholder="Metadata" [(ngModel)]="agentMetadata">
</mat-form-field>
<mat-form-field id="agent-restart-policy-select">
<mat-label>Restart policy</mat-label>
<mat-select [(value)]="agentRestartPolicySelected">
@for (policy of AGENT_RESTART_POLICIES; track policy) {
<mat-option [value]="policy.value" [id]="'mat-option-' + policy.viewValue">
{{policy.viewValue}}
</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<mat-divider></mat-divider>
<mat-form-field id="response-text-area" appearance="fill"> <mat-form-field id="response-text-area" appearance="fill">
<textarea id="api-response-text-area" [(ngModel)]="response" matInput readonly></textarea> <textarea id="api-response-text-area" [(ngModel)]="response" matInput readonly></textarea>
<button mat-button id="clear-response-text-area-btn" (click)="response=''">Clear</button> <button mat-button id="clear-response-text-area-btn" (click)="response=''">Clear</button>

View File

@ -13,7 +13,7 @@ import { MatSelectModule } from '@angular/material/select';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { VideoCodec } from '@livekit/protocol'; import { JobRestartPolicy, VideoCodec } from '@livekit/protocol';
import { LocalParticipant } from 'livekit-client'; import { LocalParticipant } from 'livekit-client';
import { import {
@ -84,6 +84,17 @@ export class RoomApiDialogComponent {
ingressUrlType: string = 'HTTP'; ingressUrlType: string = 'HTTP';
ingressUrlUri: string; ingressUrlUri: string;
agentRoomName: string;
agentName: string = 'speech-processing';
agentDispatchId: string;
agentMetadata: string;
agentRestartPolicySelected: JobRestartPolicy = JobRestartPolicy.JRP_ON_FAILURE;
AGENT_RESTART_POLICIES: { value: JobRestartPolicy; viewValue: string }[] = [
{ value: JobRestartPolicy.JRP_ON_FAILURE, viewValue: 'On failure' },
{ value: JobRestartPolicy.JRP_NEVER, viewValue: 'Never' },
];
response: string; response: string;
INGRESS_INPUT_TYPES: { value: IngressInput; viewValue: string }[] = [ INGRESS_INPUT_TYPES: { value: IngressInput; viewValue: string }[] = [
@ -141,6 +152,7 @@ export class RoomApiDialogComponent {
.values() .values()
.next().value?.trackSid!; .next().value?.trackSid!;
this.ingressRoomName = this.room?.name; this.ingressRoomName = this.room?.name;
this.agentRoomName = this.room?.name;
} }
async listRooms() { async listRooms() {
@ -365,6 +377,81 @@ export class RoomApiDialogComponent {
} }
} }
async createDispatch() {
console.log('Creating agent dispatch');
try {
const dispatch = await this.roomApiService.createDispatch(
this.agentRoomName,
this.agentName,
this.agentMetadata || undefined,
this.agentRestartPolicySelected
);
this.response = JSON.stringify(dispatch, null, 4);
this.agentDispatchId = dispatch.id;
} catch (error: any) {
this.response = error;
}
}
async getDispatch() {
console.log('Getting agent dispatch');
try {
const dispatch = await this.roomApiService.getDispatch(
this.agentDispatchId,
this.agentRoomName
);
this.response = dispatch
? JSON.stringify(dispatch, null, 4)
: 'Dispatch not found';
} catch (error: any) {
this.response = error;
}
}
async listDispatch() {
console.log('Listing agent dispatches');
try {
const dispatches = await this.roomApiService.listDispatch(
this.agentRoomName
);
this.response = JSON.stringify(dispatches, null, 4);
} catch (error: any) {
this.response = error;
}
}
async deleteDispatch() {
console.log('Deleting agent dispatch');
try {
await this.roomApiService.deleteDispatch(
this.agentDispatchId,
this.agentRoomName
);
this.response = 'Agent dispatch deleted';
} catch (error: any) {
this.response = error;
}
}
async deleteAllDispatch() {
console.log('Deleting all agent dispatches');
try {
const promises: Promise<void>[] = [];
const dispatches = await this.roomApiService.listDispatch(
this.agentRoomName
);
dispatches.forEach((d) => {
promises.push(
this.roomApiService.deleteDispatch(d.id, this.agentRoomName)
);
});
await Promise.all(promises);
this.response = 'Deleted ' + promises.length + ' agent dispatches';
} catch (error: any) {
this.response = error;
}
}
addRtmpUrl(event: MatChipInputEvent): void { addRtmpUrl(event: MatChipInputEvent): void {
const value = (event.value || '').trim(); const value = (event.value || '').trim();
if (value) { if (value) {

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { import {
AccessToken, AccessToken,
AgentDispatchClient,
CreateIngressOptions, CreateIngressOptions,
DirectFileOutput, DirectFileOutput,
EgressClient, EgressClient,
@ -25,7 +26,9 @@ import {
import { LivekitParamsService } from './livekit-params.service'; import { LivekitParamsService } from './livekit-params.service';
import { VideoQuality } from 'livekit-client'; import { VideoQuality } from 'livekit-client';
import { import {
AgentDispatch,
IngressVideoEncodingOptions, IngressVideoEncodingOptions,
JobRestartPolicy,
VideoCodec, VideoCodec,
VideoLayer, VideoLayer,
} from '@livekit/protocol'; } from '@livekit/protocol';
@ -46,6 +49,7 @@ export class RoomApiService {
private roomServiceClient: RoomServiceClient; private roomServiceClient: RoomServiceClient;
private egressClient: EgressClient; private egressClient: EgressClient;
private ingressClient: IngressClient; private ingressClient: IngressClient;
private agentDispatchClient: AgentDispatchClient;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@ -66,6 +70,11 @@ export class RoomApiService {
this.livekitParamsService.getParams().livekitApiKey, this.livekitParamsService.getParams().livekitApiKey,
this.livekitParamsService.getParams().livekitApiSecret this.livekitParamsService.getParams().livekitApiSecret
); );
this.agentDispatchClient = new AgentDispatchClient(
this.getRestUrl(),
this.livekitParamsService.getParams().livekitApiKey,
this.livekitParamsService.getParams().livekitApiSecret
);
} }
async createToken( async createToken(
@ -328,6 +337,38 @@ export class RoomApiService {
return await ingressClient.deleteIngress(ingressId); return await ingressClient.deleteIngress(ingressId);
} }
/*
* Agent Dispatch API
* https://docs.livekit.io/reference/agents/agent-dispatch-service-api/
*/
async createDispatch(
roomName: string,
agentName: string,
metadata?: string,
restartPolicy?: JobRestartPolicy
): Promise<AgentDispatch> {
return await this.agentDispatchClient.createDispatch(roomName, agentName, {
metadata,
restartPolicy,
});
}
async getDispatch(
dispatchId: string,
roomName: string
): Promise<AgentDispatch | undefined> {
return await this.agentDispatchClient.getDispatch(dispatchId, roomName);
}
async listDispatch(roomName: string): Promise<AgentDispatch[]> {
return await this.agentDispatchClient.listDispatch(roomName);
}
async deleteDispatch(dispatchId: string, roomName: string): Promise<void> {
return await this.agentDispatchClient.deleteDispatch(dispatchId, roomName);
}
private getRestUrl() { private getRestUrl() {
const wsUrl = this.livekitParamsService.getParams().livekitUrl; const wsUrl = this.livekitParamsService.getParams().livekitUrl;
const protocol = const protocol =