@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/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
- // @ts-ignore
268
- this.webex.internal.llm.socket.send({
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: this.webex.internal.llm.getBinding(),
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
- // @ts-ignore
322
- if (!this.webex.internal.llm.isConnected()) return;
323
- // @ts-ignore
324
- this.webex.internal.llm.socket.send({
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: this.webex.internal.llm.getBinding(),
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
- // @ts-ignore
364
- if (!this.webex.internal.llm.isConnected()) return;
405
+ if (!this.isLLMConnected()) {
406
+ return;
407
+ }
365
408
 
366
- // @ts-ignore
367
- this.webex.internal.llm.socket.send({
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: this.webex.internal.llm.getBinding(),
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.isAnnounceProcessing()) return;
448
- // @ts-ignore
449
- if (!this.webex.internal.llm.isConnected()) {
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
- // @ts-ignore
470
- if (!this.webex.internal.llm.isConnected()) {
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;