@webex/plugin-meetings 2.60.1-next.14 → 2.60.1-next.16

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.
@@ -37,7 +37,7 @@ import {
37
37
  EVENT_TRIGGERS as VOICEAEVENTS,
38
38
  TURN_ON_CAPTION_STATUS,
39
39
  } from '@webex/internal-plugin-voicea';
40
- import {processNewCaptions, processHighlightCreated} from './voicea-meeting';
40
+ import {processNewCaptions} from './voicea-meeting';
41
41
 
42
42
  import {
43
43
  MeetingNotActiveError,
@@ -187,7 +187,6 @@ export type Transcription = {
187
187
  isListening: boolean;
188
188
  commandText: string;
189
189
  captions: Array<CaptionData>;
190
- highlights: Array<any>;
191
190
  showCaptionBox: boolean;
192
191
  transcribingRequestStatus: string;
193
192
  isCaptioning: boolean;
@@ -664,9 +663,6 @@ export default class Meeting extends StatelessWebexPlugin {
664
663
  }
665
664
  );
666
665
  },
667
- [VOICEAEVENTS.HIGHLIGHT_CREATED]: (data) => {
668
- processHighlightCreated({data, meeting: this});
669
- },
670
666
  };
671
667
 
672
668
  private retriedWithTurnServer: boolean;
@@ -1269,7 +1265,6 @@ export default class Meeting extends StatelessWebexPlugin {
1269
1265
  */
1270
1266
  this.transcription = {
1271
1267
  captions: [],
1272
- highlights: [],
1273
1268
  isListening: false,
1274
1269
  commandText: '',
1275
1270
  languageOptions: {},
@@ -2056,12 +2051,6 @@ export default class Meeting extends StatelessWebexPlugin {
2056
2051
  this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
2057
2052
  );
2058
2053
 
2059
- // @ts-ignore
2060
- this.webex.internal.voicea.on(
2061
- VOICEAEVENTS.HIGHLIGHT_CREATED,
2062
- this.voiceaListenerCallbacks[VOICEAEVENTS.HIGHLIGHT_CREATED]
2063
- );
2064
-
2065
2054
  this.areVoiceaEventsSetup = true;
2066
2055
  }
2067
2056
 
@@ -4710,11 +4699,13 @@ export default class Meeting extends StatelessWebexPlugin {
4710
4699
  reject(payload);
4711
4700
  }
4712
4701
  };
4702
+
4713
4703
  // @ts-ignore
4714
4704
  this.webex.internal.voicea.on(
4715
4705
  VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
4716
4706
  voiceaListenerLanguageUpdate
4717
4707
  );
4708
+
4718
4709
  // @ts-ignore
4719
4710
  this.webex.internal.voicea.setSpokenLanguage(language);
4720
4711
  } catch (error) {
@@ -4830,12 +4821,6 @@ export default class Meeting extends StatelessWebexPlugin {
4830
4821
  this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
4831
4822
  );
4832
4823
 
4833
- // @ts-ignore
4834
- this.webex.internal.voicea.off(
4835
- VOICEAEVENTS.HIGHLIGHT_CREATED,
4836
- this.voiceaListenerCallbacks[VOICEAEVENTS.HIGHLIGHT_CREATED]
4837
- );
4838
-
4839
4824
  this.areVoiceaEventsSetup = false;
4840
4825
  this.triggerStopReceivingTranscriptionEvent();
4841
4826
  }
@@ -202,7 +202,11 @@ const MeetingUtil = {
202
202
  meeting.reconnectionManager.cleanUp();
203
203
  })
204
204
  .then(() => meeting.stopKeepAlive())
205
- .then(() => meeting.updateLLMConnection());
205
+ .then(() => {
206
+ if (meeting.config?.enableAutomaticLLM) {
207
+ meeting.updateLLMConnection();
208
+ }
209
+ });
206
210
  },
207
211
 
208
212
  disconnectPhoneAudio: (meeting, phoneUrl) => {
@@ -15,21 +15,18 @@ export const getSpeakerFromProxyOrStore = ({csisKey, meetingMembers, transcriptD
15
15
 
16
16
  if (csisKey && transcriptData.speakerProxy[csisKey]) {
17
17
  speaker = transcriptData.speakerProxy[csisKey];
18
+
19
+ return {speaker, needsCaching};
18
20
  }
21
+
19
22
  const meetingMember: any = getSpeaker(meetingMembers, [csisKey]);
20
23
 
21
- const speakerInStore = {
24
+ speaker = {
22
25
  speakerId: meetingMember?.participant.person.id ?? '',
23
26
  name: meetingMember?.participant.person.name ?? '',
24
27
  };
25
28
 
26
- if (
27
- meetingMember &&
28
- (speakerInStore.speakerId !== speaker.speakerId || speakerInStore.name !== speaker.name)
29
- ) {
30
- needsCaching = true;
31
- speaker = speakerInStore;
32
- }
29
+ needsCaching = true;
33
30
 
34
31
  return {speaker, needsCaching};
35
32
  };
@@ -39,20 +36,16 @@ export const processNewCaptions = ({data, meeting}) => {
39
36
  const transcriptData = meeting.transcription;
40
37
 
41
38
  if (data.isFinal) {
42
- const doesInterimTranscriptionExist = transcriptId in transcriptData.interimCaptions;
43
-
44
- if (doesInterimTranscriptionExist) {
45
- transcriptData.interimCaptions[transcriptId].forEach((fakeId) => {
46
- const fakeTranscriptIndex = transcriptData.captions.findIndex(
47
- (transcript) => transcript.id === fakeId
48
- );
49
-
50
- if (fakeTranscriptIndex !== -1) {
51
- transcriptData.captions.splice(fakeTranscriptIndex, 1);
52
- }
53
- });
54
- delete transcriptData.interimCaptions[transcriptId];
55
- }
39
+ transcriptData.interimCaptions[transcriptId].forEach((interimId) => {
40
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
41
+ (transcript) => transcript.id === interimId
42
+ );
43
+
44
+ if (interimTranscriptIndex !== -1) {
45
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
46
+ }
47
+ });
48
+ delete transcriptData.interimCaptions[transcriptId];
56
49
  const csisKey = data.transcript?.csis[0];
57
50
 
58
51
  const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
@@ -69,7 +62,7 @@ export const processNewCaptions = ({data, meeting}) => {
69
62
  isFinal: data.isFinal,
70
63
  translations: data.translations,
71
64
  text: data.transcript?.text,
72
- currentSpokenLanguage: data.transcript?.transcriptLanguageCode,
65
+ currentSpokenLanguage: data.transcript?.transcript_language_code,
73
66
  timestamp: data.timestamp,
74
67
  speaker,
75
68
  };
@@ -85,12 +78,12 @@ export const processNewCaptions = ({data, meeting}) => {
85
78
  csis: [csisMember],
86
79
  } = transcript;
87
80
 
88
- const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`;
81
+ const newCaption = `${transcriptsPerCsis.get(csisMember)?.text ?? ''} ${text}`.trim();
89
82
 
90
83
  // eslint-disable-next-line camelcase
91
84
  transcriptsPerCsis.set(csisMember, {text: newCaption, currentSpokenLanguage});
92
85
  }
93
- const fakeTranscriptionIds = [];
86
+ const interimTranscriptionIds = [];
94
87
 
95
88
  for (const [key, value] of transcriptsPerCsis) {
96
89
  const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
@@ -103,9 +96,9 @@ export const processNewCaptions = ({data, meeting}) => {
103
96
  transcriptData.speakerProxy[key] = speaker;
104
97
  }
105
98
  const {speakerId} = speaker;
106
- const fakeId = `${transcriptId}_${speakerId}`;
99
+ const interimId = `${transcriptId}_${speakerId}`;
107
100
  const captionData = {
108
- id: fakeId,
101
+ id: interimId,
109
102
  isFinal: data.isFinal,
110
103
  translations: value.translations,
111
104
  text: value.text,
@@ -114,48 +107,16 @@ export const processNewCaptions = ({data, meeting}) => {
114
107
  speaker,
115
108
  };
116
109
 
117
- const fakeTranscriptIndex = transcriptData.captions.findIndex(
118
- (transcript) => transcript.id === fakeId
110
+ const interimTranscriptIndex = transcriptData.captions.findIndex(
111
+ (transcript) => transcript.id === interimId
119
112
  );
120
113
 
121
- if (fakeTranscriptIndex !== -1) {
122
- transcriptData.captions.splice(fakeTranscriptIndex, 1);
114
+ if (interimTranscriptIndex !== -1) {
115
+ transcriptData.captions.splice(interimTranscriptIndex, 1);
123
116
  }
124
117
 
125
- fakeTranscriptionIds.push(fakeId);
118
+ interimTranscriptionIds.push(interimId);
126
119
  transcriptData.captions.push(captionData);
127
120
  }
128
- transcriptData.interimCaptions[transcriptId] = fakeTranscriptionIds;
129
- };
130
-
131
- export const processHighlightCreated = ({data, meeting}) => {
132
- const transcriptData = meeting.transcription;
133
-
134
- if (!transcriptData.highlights) {
135
- transcriptData.highlights = [];
136
- }
137
-
138
- const csisKey = data.csis && data.csis.length > 0 ? data.csis[0] : undefined;
139
- const {needsCaching, speaker} = getSpeakerFromProxyOrStore({
140
- meetingMembers: meeting.members.membersCollection.members,
141
- transcriptData,
142
- csisKey,
143
- });
144
-
145
- if (needsCaching) {
146
- transcriptData.speakerProxy[csisKey] = speaker;
147
- }
148
-
149
- const highlightCreated = {
150
- id: data.highlightId,
151
- meta: {
152
- label: data.highlightLabel,
153
- source: data.highlightSource,
154
- },
155
- text: data.text,
156
- timestamp: data.timestamp,
157
- speaker,
158
- };
159
-
160
- meeting.transcription.highlights.push(highlightCreated);
121
+ transcriptData.interimCaptions[transcriptId] = interimTranscriptionIds;
161
122
  };
@@ -676,7 +676,7 @@ describe('plugin-meetings', () => {
676
676
 
677
677
  await meeting.startTranscription();
678
678
 
679
- assert.equal(webex.internal.voicea.on.callCount, 5);
679
+ assert.equal(webex.internal.voicea.on.callCount, 4);
680
680
  assert.equal(meeting.areVoiceaEventsSetup, true);
681
681
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
682
682
  assert.calledWith(
@@ -685,7 +685,7 @@ describe('plugin-meetings', () => {
685
685
  );
686
686
 
687
687
  await meeting.startTranscription();
688
- assert.equal(webex.internal.voicea.on.callCount, 5);
688
+ assert.equal(webex.internal.voicea.on.callCount, 4);
689
689
  assert.equal(meeting.areVoiceaEventsSetup, true);
690
690
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
691
691
  assert.calledTwice(
@@ -706,7 +706,7 @@ describe('plugin-meetings', () => {
706
706
 
707
707
  await meeting.startTranscription();
708
708
 
709
- assert.equal(webex.internal.voicea.on.callCount, 5);
709
+ assert.equal(webex.internal.voicea.on.callCount, 4);
710
710
  assert.equal(meeting.areVoiceaEventsSetup, true);
711
711
  assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
712
712
  assert.notCalled(
@@ -735,7 +735,7 @@ describe('plugin-meetings', () => {
735
735
 
736
736
  it('should stop listening to voicea events and also trigger a stop event', () => {
737
737
  meeting.stopTranscription();
738
- assert.equal(webex.internal.voicea.off.callCount, 5);
738
+ assert.equal(webex.internal.voicea.off.callCount, 4);
739
739
  assert.equal(meeting.areVoiceaEventsSetup, false);
740
740
  assert.calledWith(
741
741
  TriggerProxy.trigger,
@@ -749,6 +749,150 @@ describe('plugin-meetings', () => {
749
749
  });
750
750
  });
751
751
 
752
+ describe('#setCaptionLanguage', () => {
753
+ beforeEach(() => {
754
+ meeting.isTranscriptionSupported = sinon.stub();
755
+ meeting.transcription = { languageOptions: {} };
756
+ webex.internal.voicea.on = sinon.stub();
757
+ webex.internal.voicea.off = sinon.stub();
758
+ webex.internal.voicea.setCaptionLanguage = sinon.stub();
759
+ webex.internal.voicea.requestLanguage = sinon.stub();
760
+ });
761
+
762
+ afterEach(() => {
763
+ // Restore the original methods after each test
764
+ sinon.restore();
765
+ });
766
+
767
+ it('should reject if transcription is not supported', (done) => {
768
+ meeting.isTranscriptionSupported.returns(false);
769
+
770
+ meeting.setCaptionLanguage('fr').catch((error) => {
771
+ assert.equal(error.message, 'Webex Assistant is not enabled/supported');
772
+ done();
773
+ });
774
+ });
775
+
776
+ it('should resolve with the language code on successful language update', (done) => {
777
+ meeting.isTranscriptionSupported.returns(true);
778
+ const languageCode = 'fr';
779
+
780
+ meeting.setCaptionLanguage(languageCode).then((resolvedLanguageCode) => {
781
+ assert.calledWith(
782
+ webex.internal.voicea.requestLanguage,
783
+ languageCode
784
+ );
785
+ assert.equal(resolvedLanguageCode, languageCode);
786
+ assert.equal(meeting.transcription.languageOptions.currentCaptionLanguage, languageCode);
787
+ done();
788
+ });
789
+
790
+ assert.calledOnceWithMatch(
791
+ webex.internal.voicea.on,
792
+ VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
793
+ );
794
+
795
+ // Trigger the event
796
+ const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
797
+ voiceaListenerLangugeUpdate({ statusCode: 200, languageCode });
798
+ });
799
+
800
+ it('should reject if the statusCode in payload is not 200', (done) => {
801
+ meeting.isTranscriptionSupported.returns(true);
802
+ const languageCode = 'fr';
803
+ const rejectPayload = {
804
+ statusCode: 400,
805
+ message: 'some error message'
806
+ }
807
+
808
+ meeting.setCaptionLanguage(languageCode).catch((payload) => {
809
+ assert.equal(payload, rejectPayload);
810
+ done();
811
+ });
812
+
813
+ assert.calledOnceWithMatch(
814
+ webex.internal.voicea.on,
815
+ VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
816
+ );
817
+
818
+ // Trigger the event
819
+ const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
820
+ voiceaListenerLangugeUpdate(rejectPayload);
821
+ });
822
+
823
+ });
824
+
825
+ describe('#setSpokenLanguage', () => {
826
+ beforeEach(() => {
827
+ meeting.isTranscriptionSupported = sinon.stub();
828
+ meeting.transcription = { languageOptions: {} };
829
+ webex.internal.voicea.on = sinon.stub();
830
+ webex.internal.voicea.off = sinon.stub();
831
+ webex.internal.voicea.setSpokenLanguage = sinon.stub();
832
+ });
833
+
834
+ afterEach(() => {
835
+ // Restore the original methods after each test
836
+ sinon.restore();
837
+ });
838
+
839
+ it('should reject if transcription is not supported', (done) => {
840
+ meeting.isTranscriptionSupported.returns(false);
841
+
842
+ meeting.setSpokenLanguage('fr').catch((error) => {
843
+ assert.equal(error.message, 'Webex Assistant is not enabled/supported');
844
+ done();
845
+ });
846
+ });
847
+
848
+ it('should resolve with the language code on successful language update', (done) => {
849
+ meeting.isTranscriptionSupported.returns(true);
850
+ const languageCode = 'fr';
851
+
852
+ meeting.setSpokenLanguage(languageCode).then((resolvedLanguageCode) => {
853
+ assert.calledWith(
854
+ webex.internal.voicea.setSpokenLanguage,
855
+ languageCode
856
+ );
857
+ assert.equal(resolvedLanguageCode, languageCode);
858
+ assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, languageCode);
859
+ done();
860
+ });
861
+
862
+ assert.calledOnceWithMatch(
863
+ webex.internal.voicea.on,
864
+ VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
865
+ );
866
+
867
+ // Trigger the event
868
+ const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
869
+ voiceaListenerLangugeUpdate({ languageCode });
870
+ });
871
+
872
+ it('should reject if the language code does not exist in payload', (done) => {
873
+ meeting.isTranscriptionSupported.returns(true);
874
+ const languageCode = 'fr';
875
+ const rejectPayload = {
876
+ 'message': 'some error message'
877
+ }
878
+
879
+ meeting.setSpokenLanguage(languageCode).catch((payload) => {
880
+ assert.equal(payload, rejectPayload);
881
+ done();
882
+ });
883
+
884
+ assert.calledOnceWithMatch(
885
+ webex.internal.voicea.on,
886
+ VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
887
+ );
888
+
889
+ // Trigger the event
890
+ const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
891
+ voiceaListenerLangugeUpdate(rejectPayload);
892
+ });
893
+
894
+ });
895
+
752
896
  describe('transcription events', () => {
753
897
  it('should trigger meeting:caption-received event', () => {
754
898
  meeting.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]({});
@@ -789,6 +933,7 @@ describe('plugin-meetings', () => {
789
933
  );
790
934
  });
791
935
  });
936
+
792
937
  describe('#isReactionsSupported', () => {
793
938
  it('should return false if the feature is not supported for the meeting', () => {
794
939
  meeting.locusInfo.controls = {reactions: {enabled: false}};
@@ -57,7 +57,8 @@ describe('plugin-meetings', () => {
57
57
  });
58
58
 
59
59
  describe('#cleanup', () => {
60
- it('do clean up on meeting object', async () => {
60
+ it('do clean up on meeting object with LLM enabled', async () => {
61
+ meeting.config = {enableAutomaticLLM : true};
61
62
  await MeetingUtil.cleanUp(meeting);
62
63
  assert.calledOnce(meeting.cleanupLocalStreams);
63
64
  assert.calledOnce(meeting.closeRemoteStreams);
@@ -71,6 +72,37 @@ describe('plugin-meetings', () => {
71
72
  assert.calledOnce(meeting.breakouts.cleanUp);
72
73
  assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
73
74
  });
75
+
76
+ it('do clean up on meeting object with LLM disabled', async () => {
77
+ meeting.config = {enableAutomaticLLM : false};
78
+ await MeetingUtil.cleanUp(meeting);
79
+ assert.calledOnce(meeting.cleanupLocalStreams);
80
+ assert.calledOnce(meeting.closeRemoteStreams);
81
+ assert.calledOnce(meeting.closePeerConnections);
82
+
83
+ assert.calledOnce(meeting.unsetRemoteStreams);
84
+ assert.calledOnce(meeting.unsetPeerConnections);
85
+ assert.calledOnce(meeting.reconnectionManager.cleanUp);
86
+ assert.calledOnce(meeting.stopKeepAlive);
87
+ assert.notCalled(meeting.updateLLMConnection);
88
+ assert.calledOnce(meeting.breakouts.cleanUp);
89
+ assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
90
+ });
91
+
92
+ it('do clean up on meeting object with no config', async () => {
93
+ await MeetingUtil.cleanUp(meeting);
94
+ assert.calledOnce(meeting.cleanupLocalStreams);
95
+ assert.calledOnce(meeting.closeRemoteStreams);
96
+ assert.calledOnce(meeting.closePeerConnections);
97
+
98
+ assert.calledOnce(meeting.unsetRemoteStreams);
99
+ assert.calledOnce(meeting.unsetPeerConnections);
100
+ assert.calledOnce(meeting.reconnectionManager.cleanUp);
101
+ assert.calledOnce(meeting.stopKeepAlive);
102
+ assert.notCalled(meeting.updateLLMConnection);
103
+ assert.calledOnce(meeting.breakouts.cleanUp);
104
+ assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
105
+ });
74
106
  });
75
107
 
76
108
  describe('logging', () => {