@webex/internal-plugin-voicea 3.11.0 → 3.12.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/voicea.d.ts +47 -0
- package/dist/voicea.js +143 -18
- package/dist/voicea.js.map +1 -1
- package/package.json +9 -9
- package/src/constants.ts +1 -0
- package/src/voicea.ts +132 -18
- package/test/unit/spec/voicea.js +392 -18
package/src/voicea.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
AIBRIDGE_RELAY_TYPES,
|
|
7
7
|
TRANSCRIPTION_TYPE,
|
|
8
8
|
VOICEA,
|
|
9
|
+
LLM_PRACTICE_SESSION,
|
|
9
10
|
ANNOUNCE_STATUS,
|
|
10
11
|
TURN_ON_CAPTION_STATUS,
|
|
11
12
|
TOGGLE_MANUAL_CAPTION_STATUS,
|
|
@@ -41,6 +42,8 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
41
42
|
|
|
42
43
|
private captionStatus: string;
|
|
43
44
|
|
|
45
|
+
private isCaptionBoxOn: boolean;
|
|
46
|
+
|
|
44
47
|
private toggleManualCaptionStatus: string;
|
|
45
48
|
|
|
46
49
|
private currentSpokenLanguage?: string;
|
|
@@ -89,6 +92,8 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
89
92
|
if (!this.hasSubscribedToEvents) {
|
|
90
93
|
// @ts-ignore
|
|
91
94
|
this.webex.internal.llm.on('event:relay.event', this.eventProcessor);
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
this.webex.internal.llm.on(`event:relay.event:${LLM_PRACTICE_SESSION}`, this.eventProcessor);
|
|
92
97
|
this.hasSubscribedToEvents = true;
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -99,9 +104,12 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
99
104
|
*/
|
|
100
105
|
public deregisterEvents() {
|
|
101
106
|
this.areCaptionsEnabled = false;
|
|
107
|
+
this.isCaptionBoxOn = false;
|
|
102
108
|
this.captionServiceId = undefined;
|
|
103
109
|
// @ts-ignore
|
|
104
110
|
this.webex.internal.llm.off('event:relay.event', this.eventProcessor);
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
this.webex.internal.llm.off(`event:relay.event:${LLM_PRACTICE_SESSION}`, this.eventProcessor);
|
|
105
113
|
this.hasSubscribedToEvents = false;
|
|
106
114
|
this.announceStatus = ANNOUNCE_STATUS.IDLE;
|
|
107
115
|
this.captionStatus = TURN_ON_CAPTION_STATUS.IDLE;
|
|
@@ -257,6 +265,38 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
257
265
|
this.trigger(EVENT_TRIGGERS.VOICEA_ANNOUNCEMENT, voiceaLanguageOptions);
|
|
258
266
|
};
|
|
259
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Indicates whether the default or practice-session LLM connection is active.
|
|
270
|
+
* @returns {boolean}
|
|
271
|
+
*/
|
|
272
|
+
private isLLMConnected = (): boolean =>
|
|
273
|
+
// @ts-ignore
|
|
274
|
+
this.webex.internal.llm.isConnected() ||
|
|
275
|
+
// @ts-ignore
|
|
276
|
+
this.webex.internal.llm.isConnected(LLM_PRACTICE_SESSION);
|
|
277
|
+
|
|
278
|
+
public getIsCaptionBoxOn = (): boolean => this.isCaptionBoxOn;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolves the active LLM publish transport, preferring the practice-session
|
|
282
|
+
* connection only when that session is fully connected.
|
|
283
|
+
* @returns {Object}
|
|
284
|
+
*/
|
|
285
|
+
private getPublishTransport = () => {
|
|
286
|
+
// @ts-ignore
|
|
287
|
+
const {llm} = this.webex.internal;
|
|
288
|
+
const isPracticeSessionConnected = llm.isConnected(LLM_PRACTICE_SESSION);
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
socket: (isPracticeSessionConnected && llm.getSocket(LLM_PRACTICE_SESSION)) || llm.socket,
|
|
292
|
+
binding:
|
|
293
|
+
(isPracticeSessionConnected && llm.getBinding(LLM_PRACTICE_SESSION)) || llm.getBinding(),
|
|
294
|
+
datachannelUrl:
|
|
295
|
+
(isPracticeSessionConnected && llm.getDatachannelUrl(LLM_PRACTICE_SESSION)) ||
|
|
296
|
+
llm.getDatachannelUrl(),
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
|
|
260
300
|
/**
|
|
261
301
|
* Sends Announcement to add voicea to the meeting
|
|
262
302
|
* @returns {void}
|
|
@@ -264,13 +304,13 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
264
304
|
private sendAnnouncement = (): void => {
|
|
265
305
|
this.announceStatus = ANNOUNCE_STATUS.JOINING;
|
|
266
306
|
this.listenToEvents();
|
|
267
|
-
|
|
268
|
-
|
|
307
|
+
const {socket, binding} = this.getPublishTransport();
|
|
308
|
+
socket.send({
|
|
269
309
|
id: `${this.seqNum}`,
|
|
270
310
|
type: 'publishRequest',
|
|
271
311
|
recipients: {
|
|
272
312
|
// @ts-ignore
|
|
273
|
-
route:
|
|
313
|
+
route: binding,
|
|
274
314
|
},
|
|
275
315
|
// If captionServiceId exists, send it as the 'to' header; otherwise keep headers empty.
|
|
276
316
|
headers: this.captionServiceId ? {to: this.captionServiceId} : {},
|
|
@@ -318,15 +358,17 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
318
358
|
* @returns {void}
|
|
319
359
|
*/
|
|
320
360
|
public requestLanguage = (languageCode: string): void => {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
361
|
+
if (!this.isLLMConnected()) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const {socket, binding} = this.getPublishTransport();
|
|
366
|
+
socket.send({
|
|
325
367
|
id: `${this.seqNum}`,
|
|
326
368
|
type: 'publishRequest',
|
|
327
369
|
recipients: {
|
|
328
370
|
// @ts-ignore
|
|
329
|
-
route:
|
|
371
|
+
route: binding,
|
|
330
372
|
},
|
|
331
373
|
headers: {
|
|
332
374
|
to: this.captionServiceId,
|
|
@@ -360,16 +402,18 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
360
402
|
csis: number[],
|
|
361
403
|
isFinal: boolean
|
|
362
404
|
): void => {
|
|
363
|
-
|
|
364
|
-
|
|
405
|
+
if (!this.isLLMConnected()) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
365
408
|
|
|
366
|
-
|
|
367
|
-
|
|
409
|
+
const {socket, binding} = this.getPublishTransport();
|
|
410
|
+
|
|
411
|
+
socket?.send({
|
|
368
412
|
id: `${this.seqNum}`,
|
|
369
413
|
type: 'publishRequest',
|
|
370
414
|
recipients: {
|
|
371
415
|
// @ts-ignore
|
|
372
|
-
route:
|
|
416
|
+
route: binding,
|
|
373
417
|
},
|
|
374
418
|
headers: {},
|
|
375
419
|
data: {
|
|
@@ -425,6 +469,7 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
425
469
|
this.areCaptionsEnabled = true;
|
|
426
470
|
this.captionStatus = TURN_ON_CAPTION_STATUS.ENABLED;
|
|
427
471
|
this.announce();
|
|
472
|
+
this.updateSubchannelSubscriptionsAndSyncCaptionState({subscribe: ['transcription']}, true);
|
|
428
473
|
})
|
|
429
474
|
.catch(() => {
|
|
430
475
|
this.captionStatus = TURN_ON_CAPTION_STATUS.IDLE;
|
|
@@ -439,14 +484,21 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
439
484
|
private isAnnounceProcessing = () =>
|
|
440
485
|
[ANNOUNCE_STATUS.JOINING, ANNOUNCE_STATUS.JOINED].includes(this.announceStatus);
|
|
441
486
|
|
|
487
|
+
/**
|
|
488
|
+
* is announce processed
|
|
489
|
+
* @returns {boolean}
|
|
490
|
+
*/
|
|
491
|
+
private isAnnounceProcessed = () => this.announceStatus === ANNOUNCE_STATUS.JOINED;
|
|
492
|
+
|
|
442
493
|
/**
|
|
443
494
|
* announce to voicea data chanel
|
|
444
495
|
* @returns {void}
|
|
445
496
|
*/
|
|
446
497
|
public announce = () => {
|
|
447
|
-
if (this.
|
|
448
|
-
|
|
449
|
-
|
|
498
|
+
if (this.isAnnounceProcessed()) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
if (!this.isLLMConnected()) {
|
|
450
502
|
throw new Error('voicea can not announce before llm connected');
|
|
451
503
|
}
|
|
452
504
|
this.sendAnnouncement();
|
|
@@ -466,8 +518,8 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
466
518
|
*/
|
|
467
519
|
public turnOnCaptions = async (spokenLanguage?): undefined | Promise<void> => {
|
|
468
520
|
if (this.captionStatus === TURN_ON_CAPTION_STATUS.SENDING) return undefined;
|
|
469
|
-
|
|
470
|
-
if (!this.
|
|
521
|
+
|
|
522
|
+
if (!this.isLLMConnected()) {
|
|
471
523
|
throw new Error('can not turn on captions before llm connected');
|
|
472
524
|
}
|
|
473
525
|
|
|
@@ -577,6 +629,68 @@ export class VoiceaChannel extends WebexPlugin implements IVoiceaChannel {
|
|
|
577
629
|
* @returns {string}
|
|
578
630
|
*/
|
|
579
631
|
public getAnnounceStatus = () => this.announceStatus;
|
|
632
|
+
/**
|
|
633
|
+
* update LLM sub‑channel subscriptions.
|
|
634
|
+
*
|
|
635
|
+
* sends a single `subchannelSubscriptionRequest` to LLM,
|
|
636
|
+
* allowing subscribe and unsubscribe subchannel.
|
|
637
|
+
*
|
|
638
|
+
* @param {string[]} options.subscribe Sub‑channels to subscribe to.
|
|
639
|
+
* @param {string[]} options.unsubscribe Sub‑channels to unsubscribe from.
|
|
640
|
+
* @returns {Promise}
|
|
641
|
+
*/
|
|
642
|
+
public updateSubchannelSubscriptions = async ({
|
|
643
|
+
subscribe = [],
|
|
644
|
+
unsubscribe = [],
|
|
645
|
+
}: {
|
|
646
|
+
subscribe?: string[];
|
|
647
|
+
unsubscribe?: string[];
|
|
648
|
+
} = {}): Promise<void> => {
|
|
649
|
+
// @ts-ignore
|
|
650
|
+
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
651
|
+
// @ts-ignore
|
|
652
|
+
if (!this.isLLMConnected() || !isDataChannelTokenEnabled) return;
|
|
653
|
+
|
|
654
|
+
const {socket, datachannelUrl} = this.getPublishTransport();
|
|
655
|
+
|
|
656
|
+
// @ts-ignore
|
|
657
|
+
socket.send({
|
|
658
|
+
id: `${this.seqNum}`,
|
|
659
|
+
type: 'subchannelSubscriptionRequest',
|
|
660
|
+
data: {
|
|
661
|
+
// @ts-ignore
|
|
662
|
+
datachannelUri: datachannelUrl,
|
|
663
|
+
subscribe,
|
|
664
|
+
unsubscribe,
|
|
665
|
+
},
|
|
666
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
this.seqNum += 1;
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Syncs the UI caption intent and updates transcription subchannel
|
|
674
|
+
* subscriptions accordingly.
|
|
675
|
+
*
|
|
676
|
+
* @param {Object} [options] - Subscription options.
|
|
677
|
+
* @param {string[]} [options.subscribe] - Subchannels to subscribe to.
|
|
678
|
+
* @param {string[]} [options.unsubscribe] - Subchannels to unsubscribe from.
|
|
679
|
+
* @param {boolean} [isCaptionBoxOn=false] - Whether captions are intended to be enabled.
|
|
680
|
+
*
|
|
681
|
+
* @returns {Promise<void>}
|
|
682
|
+
*/
|
|
683
|
+
public updateSubchannelSubscriptionsAndSyncCaptionState = (
|
|
684
|
+
options: {
|
|
685
|
+
subscribe?: string[];
|
|
686
|
+
unsubscribe?: string[];
|
|
687
|
+
} = {},
|
|
688
|
+
isCaptionBoxOn = false
|
|
689
|
+
): Promise<void> => {
|
|
690
|
+
this.isCaptionBoxOn = isCaptionBoxOn;
|
|
691
|
+
|
|
692
|
+
return this.updateSubchannelSubscriptions(options);
|
|
693
|
+
};
|
|
580
694
|
}
|
|
581
695
|
|
|
582
696
|
export default VoiceaChannel;
|