agora-appbuilder-core 4.1.10-beta.1 → 4.1.11-beta.2

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.
Files changed (132) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
  3. package/template/android/app/build.gradle +0 -7
  4. package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
  5. package/template/bridge/rtm/web/Types.ts +0 -183
  6. package/template/bridge/rtm/web/index.ts +488 -450
  7. package/template/customization-api/typeDefinition.ts +0 -1
  8. package/template/defaultConfig.js +3 -4
  9. package/template/global.d.ts +0 -1
  10. package/template/ios/Podfile +0 -41
  11. package/template/package.json +5 -5
  12. package/template/src/AppRoutes.tsx +3 -3
  13. package/template/src/ai-agent/components/ControlButtons.tsx +1 -1
  14. package/template/src/assets/font-styles.css +1 -33
  15. package/template/src/assets/fonts/icomoon.ttf +0 -0
  16. package/template/src/assets/selection.json +1 -1
  17. package/template/src/atoms/ActionMenu.tsx +93 -13
  18. package/template/src/atoms/CustomIcon.tsx +1 -8
  19. package/template/src/atoms/DropDownMulti.tsx +80 -29
  20. package/template/src/atoms/Dropdown.tsx +0 -5
  21. package/template/src/atoms/Input.tsx +2 -1
  22. package/template/src/atoms/TertiaryButton.tsx +1 -1
  23. package/template/src/atoms/UserAvatar.tsx +1 -1
  24. package/template/src/components/ChatContext.ts +3 -5
  25. package/template/src/components/Controls.tsx +167 -208
  26. package/template/src/components/DeviceConfigure.tsx +1 -1
  27. package/template/src/components/EventsConfigure.tsx +168 -118
  28. package/template/src/components/Navbar.tsx +11 -14
  29. package/template/src/components/RTMConfigure.tsx +819 -32
  30. package/template/src/components/beauty-effect/useBeautyEffects.tsx +13 -50
  31. package/template/src/components/chat/chatConfigure.tsx +1 -7
  32. package/template/src/components/chat-messages/useChatMessages.tsx +11 -43
  33. package/template/src/components/controls/useControlPermissionMatrix.tsx +4 -32
  34. package/template/src/components/participants/AllHostParticipants.tsx +2 -10
  35. package/template/src/components/participants/Participant.tsx +1 -7
  36. package/template/src/components/participants/UserActionMenuOptions.tsx +2 -12
  37. package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
  38. package/template/src/components/precall/joinCallBtn.tsx +7 -2
  39. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +16 -15
  40. package/template/src/components/precall/joinWaitingRoomBtn.tsx +31 -17
  41. package/template/src/components/precall/textInput.tsx +45 -22
  42. package/template/src/components/precall/usePreCall.tsx +7 -0
  43. package/template/src/components/recordings/RecordingsDateTable.tsx +2 -3
  44. package/template/src/components/room-info/useRoomInfo.tsx +5 -0
  45. package/template/src/components/useUserPreference.tsx +12 -39
  46. package/template/src/components/virtual-background/useVB.tsx +0 -18
  47. package/template/src/components/whiteboard/WhiteboardConfigure.tsx +0 -27
  48. package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -11
  49. package/template/src/logger/AppBuilderLogger.tsx +3 -11
  50. package/template/src/pages/VideoCall.tsx +518 -171
  51. package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
  52. package/template/src/pages/video-call/SidePanelHeader.tsx +81 -53
  53. package/template/src/pages/video-call/VideoCallScreen.tsx +0 -18
  54. package/template/src/pages/video-call/VideoCallScreenWrapper.tsx +1 -0
  55. package/template/src/rtm/RTMEngine.ts +37 -262
  56. package/template/src/rtm/utils.ts +1 -68
  57. package/template/src/rtm-events/constants.ts +7 -40
  58. package/template/src/rtm-events-api/Events.ts +39 -158
  59. package/template/src/subComponents/ChatBubble.tsx +3 -3
  60. package/template/src/subComponents/ChatContainer.tsx +9 -19
  61. package/template/src/subComponents/LocalAudioMute.tsx +2 -2
  62. package/template/src/subComponents/LocalVideoMute.tsx +2 -2
  63. package/template/src/subComponents/SidePanelEnum.tsx +0 -1
  64. package/template/src/subComponents/caption/Caption.tsx +48 -7
  65. package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
  66. package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
  67. package/template/src/subComponents/caption/CaptionText.tsx +103 -2
  68. package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
  69. package/template/src/subComponents/caption/Transcript.tsx +46 -11
  70. package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
  71. package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
  72. package/template/src/subComponents/caption/proto/ptoto.js +38 -4
  73. package/template/src/subComponents/caption/proto/test.proto +34 -19
  74. package/template/src/subComponents/caption/useCaption.tsx +754 -11
  75. package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
  76. package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
  77. package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
  78. package/template/src/subComponents/caption/utils.ts +171 -3
  79. package/template/src/subComponents/chat/ChatSendButton.tsx +0 -1
  80. package/template/src/subComponents/screenshare/ScreenshareButton.tsx +0 -16
  81. package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +1 -1
  82. package/template/src/subComponents/waiting-rooms/WaitingRoomControls.tsx +4 -7
  83. package/template/src/utils/SdkEvents.ts +3 -0
  84. package/template/src/utils/useEndCall.ts +4 -4
  85. package/template/src/utils/useMuteToggleLocal.ts +10 -14
  86. package/template/src/utils/useSpeechToText.ts +31 -20
  87. package/template/bridge/rtm/web/index-legacy.ts +0 -540
  88. package/template/src/components/RTMConfigure-legacy.tsx +0 -848
  89. package/template/src/components/UserGlobalPreferenceProvider.tsx +0 -227
  90. package/template/src/components/breakout-room/BreakoutRoomPanel.tsx +0 -58
  91. package/template/src/components/breakout-room/context/BreakoutRoomContext.tsx +0 -2508
  92. package/template/src/components/breakout-room/events/BreakoutRoomEventsConfigure.tsx +0 -272
  93. package/template/src/components/breakout-room/events/constants.ts +0 -17
  94. package/template/src/components/breakout-room/hoc/BreakoutRoomNameRenderer.tsx +0 -68
  95. package/template/src/components/breakout-room/hooks/useBreakoutRoomExit.ts +0 -49
  96. package/template/src/components/breakout-room/state/reducer.ts +0 -522
  97. package/template/src/components/breakout-room/state/types.ts +0 -54
  98. package/template/src/components/breakout-room/ui/BreakoutMeetingTitle.tsx +0 -60
  99. package/template/src/components/breakout-room/ui/BreakoutRoomActionMenu.tsx +0 -136
  100. package/template/src/components/breakout-room/ui/BreakoutRoomAnnouncementModal.tsx +0 -135
  101. package/template/src/components/breakout-room/ui/BreakoutRoomGroupSettings.tsx +0 -588
  102. package/template/src/components/breakout-room/ui/BreakoutRoomMainRoomUsers.tsx +0 -142
  103. package/template/src/components/breakout-room/ui/BreakoutRoomMemberActionMenu.tsx +0 -122
  104. package/template/src/components/breakout-room/ui/BreakoutRoomParticipants.tsx +0 -124
  105. package/template/src/components/breakout-room/ui/BreakoutRoomRaiseHand.tsx +0 -65
  106. package/template/src/components/breakout-room/ui/BreakoutRoomRenameModal.tsx +0 -227
  107. package/template/src/components/breakout-room/ui/BreakoutRoomSettings.tsx +0 -140
  108. package/template/src/components/breakout-room/ui/BreakoutRoomTransition.tsx +0 -52
  109. package/template/src/components/breakout-room/ui/BreakoutRoomView.tsx +0 -193
  110. package/template/src/components/breakout-room/ui/ExitBreakoutRoomIconButton.tsx +0 -79
  111. package/template/src/components/breakout-room/ui/ParticipantManualAssignmentModal.tsx +0 -638
  112. package/template/src/components/breakout-room/ui/SelectParticipantAssignmentStrategy.tsx +0 -57
  113. package/template/src/components/common/Dividers.tsx +0 -53
  114. package/template/src/components/controls/toolbar-items/ExitBreakoutRoomToolbarItem.tsx +0 -13
  115. package/template/src/components/raise-hand/RaiseHandButton.tsx +0 -50
  116. package/template/src/components/raise-hand/RaiseHandProvider.tsx +0 -308
  117. package/template/src/components/raise-hand/index.ts +0 -14
  118. package/template/src/components/room-info/useCurrentRoomInfo.tsx +0 -42
  119. package/template/src/components/room-info/useSetBreakoutRoomInfo.tsx +0 -64
  120. package/template/src/pages/video-call/BreakoutVideoCall.tsx +0 -213
  121. package/template/src/pages/video-call/VideoCallContent.tsx +0 -211
  122. package/template/src/pages/video-call/VideoCallStateWrapper.tsx +0 -495
  123. package/template/src/rtm/RTMConfigureBreakoutRoomProvider.tsx +0 -882
  124. package/template/src/rtm/RTMConfigureMainRoomProvider.tsx +0 -757
  125. package/template/src/rtm/RTMCoreProvider.tsx +0 -419
  126. package/template/src/rtm/RTMGlobalStateProvider.tsx +0 -706
  127. package/template/src/rtm/RTMStatusBanner.tsx +0 -99
  128. package/template/src/rtm/constants.ts +0 -12
  129. package/template/src/rtm/hooks/useMainRoomUserDisplayName.ts +0 -45
  130. package/template/src/rtm/rtm-presence-utils.ts +0 -344
  131. package/template/src/subComponents/chat/ChatAnnouncementView.tsx +0 -65
  132. package/template/src/utils/useDebouncedCallback.tsx +0 -20
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import {useCaption} from './useCaption';
3
2
  import protoRoot from './proto/ptoto';
4
3
  import PQueue from 'p-queue';
@@ -7,6 +6,16 @@ type StreamMessageCallback = (args: [number, Uint8Array]) => void;
7
6
  type FinalListType = {
8
7
  [key: string]: string[];
9
8
  };
9
+ type TranslationData = {
10
+ lang: string;
11
+ text: string;
12
+ isFinal: boolean;
13
+ };
14
+ type FinalTranslationListType = {
15
+ [key: string]: {
16
+ [lang: string]: string[];
17
+ };
18
+ };
10
19
 
11
20
  const useStreamMessageUtils = (): {
12
21
  streamMessageCallback: StreamMessageCallback;
@@ -16,30 +25,34 @@ const useStreamMessageUtils = (): {
16
25
  setMeetingTranscript,
17
26
  activeSpeakerRef,
18
27
  prevSpeakerRef,
28
+ // Use ref instead of state to avoid stale closure issues
29
+ // The ref always has the current value, even in callbacks created at mount time
30
+ selectedTranslationLanguageRef,
19
31
  } = useCaption();
20
32
 
21
33
  let captionStartTime: number = 0;
22
34
  const finalList: FinalListType = {};
23
35
  const finalTranscriptList: FinalListType = {};
36
+ const finalTranslationList: FinalTranslationListType = {};
24
37
  const queue = new PQueue({concurrency: 1});
25
- let counter = 0;
26
- let lastOfftime = 0;
27
38
 
28
39
  const streamMessageCallback: StreamMessageCallback = args => {
29
40
  const queueCallback = (args1: [number, Uint8Array]) => {
30
41
  /* uid - bot which sends stream message in channel
31
42
  payload - stream message in Uint8Array format
32
43
  */
33
- const [, payload] = args1;
44
+ const [botUid, payload] = args1;
34
45
  let nonFinalText = ''; // holds intermediate results
35
46
  let finalText = ''; // holds final strings
36
47
  let currentFinalText = ''; // holds current caption
37
48
  let isInterjecting = false;
49
+ let translations: TranslationData[] = []; // holds translation data
38
50
 
39
51
  const textstream = protoRoot
40
- .lookupType('Text')
52
+ .lookupType('agora.audio2text.Text')
41
53
  .decode(payload as Uint8Array) as any;
42
54
 
55
+ console.log('[STT_PER_USER_BOT] stt v7 textstream', botUid, textstream);
43
56
  //console.log('STT - Parsed Textstream : ', textstream);
44
57
  // console.log(
45
58
  // `STT-callback(${++counter}): %c${textstream.uid} %c${textstream.words
@@ -84,7 +97,9 @@ const useStreamMessageUtils = (): {
84
97
 
85
98
  */
86
99
 
87
- const finalWord = textstream.words.filter(word => word.isFinal === true);
100
+ const finalWord = textstream.words.filter(
101
+ (word: any) => word.isFinal === true,
102
+ );
88
103
  // when we only get final word for the previous speaker then don't flip previous speaker as active but update in place.
89
104
 
90
105
  if (
@@ -94,6 +109,14 @@ const useStreamMessageUtils = (): {
94
109
  // we have a speaker change so clear the context for prev speaker
95
110
  if (prevSpeakerRef.current !== '') {
96
111
  finalList[prevSpeakerRef.current] = [];
112
+ // Clear translations for previous speaker
113
+ if (finalTranslationList[prevSpeakerRef.current]) {
114
+ Object.keys(finalTranslationList[prevSpeakerRef.current]).forEach(
115
+ lang => {
116
+ finalTranslationList[prevSpeakerRef.current][lang] = [];
117
+ },
118
+ );
119
+ }
97
120
  isInterjecting = true;
98
121
  // console.log(
99
122
  // '%cSTT-callback%c Interjection! ',
@@ -114,16 +137,63 @@ const useStreamMessageUtils = (): {
114
137
  finalTranscriptList[textstream.uid] = [];
115
138
  }
116
139
 
140
+ // Process translations if available
141
+ if (textstream.trans && textstream.trans.length > 0) {
142
+ for (const trans of textstream.trans) {
143
+ const lang = trans.lang;
144
+ const texts = trans.texts || [];
145
+ const isFinal = trans.isFinal || false;
146
+
147
+ if (!finalTranslationList[textstream.uid]) {
148
+ finalTranslationList[textstream.uid] = {};
149
+ }
150
+ if (!finalTranslationList[textstream.uid][lang]) {
151
+ finalTranslationList[textstream.uid][lang] = [];
152
+ }
153
+
154
+ const currentTranslationText = texts.join(' ');
155
+ if (currentTranslationText) {
156
+ if (isFinal) {
157
+ finalTranslationList[textstream.uid][lang].push(
158
+ currentTranslationText,
159
+ );
160
+ }
161
+
162
+ // Build complete translation text (final + current non-final)
163
+ const existingTranslationBuffer = isInterjecting
164
+ ? ''
165
+ : finalTranslationList[textstream.uid][lang]?.join(' ');
166
+ const latestTranslationString = isFinal
167
+ ? ''
168
+ : currentTranslationText;
169
+ const completeTranslationText =
170
+ existingTranslationBuffer.length > 0
171
+ ? latestTranslationString
172
+ ? existingTranslationBuffer + ' ' + latestTranslationString
173
+ : existingTranslationBuffer
174
+ : latestTranslationString;
175
+
176
+ if (completeTranslationText || isFinal) {
177
+ translations.push({
178
+ lang,
179
+ text: completeTranslationText,
180
+ isFinal,
181
+ });
182
+ }
183
+ }
184
+ }
185
+ }
186
+
117
187
  const words = textstream.words; //[Word,Word]
118
188
 
119
189
  /* categorize words into final & nonFinal objects per uid
120
- Final Word Ex : {
121
- "text": "Hello, are you doing?",
122
- "durationMs": 960,
123
- "isFinal": true,
124
- "confidence": 0.8549408316612244
125
- }
126
- */
190
+ Final Word Ex : {
191
+ "text": "Hello, are you doing?",
192
+ "durationMs": 960,
193
+ "isFinal": true,
194
+ "confidence": 0.8549408316612244
195
+ }
196
+ */
127
197
 
128
198
  for (const word of words) {
129
199
  if (word.isFinal) {
@@ -150,7 +220,29 @@ const useStreamMessageUtils = (): {
150
220
  }
151
221
 
152
222
  /* Updating Meeting Transcript */
153
- if (currentFinalText.length) {
223
+ // Update transcript when: (1) new text finalized OR (2) final translations arrived
224
+ const hasFinalTranslations = textstream.trans?.some(
225
+ (t: any) => t.isFinal === true,
226
+ );
227
+
228
+ if (currentFinalText.length || hasFinalTranslations) {
229
+ // final translations for transcript - include ALL available final translations for this user
230
+ const finalTranslationsForTranscript: TranslationData[] = [];
231
+ if (finalTranslationList[textstream.uid]) {
232
+ Object.keys(finalTranslationList[textstream.uid]).forEach(lang => {
233
+ const translationText =
234
+ finalTranslationList[textstream.uid][lang]?.join(' ') || '';
235
+
236
+ if (translationText) {
237
+ finalTranslationsForTranscript.push({
238
+ lang: lang,
239
+ text: translationText,
240
+ isFinal: true,
241
+ });
242
+ }
243
+ });
244
+ }
245
+
154
246
  setMeetingTranscript(prevTranscript => {
155
247
  const lastTranscriptIndex = prevTranscript.length - 1;
156
248
  const lastTranscript =
@@ -160,21 +252,30 @@ const useStreamMessageUtils = (): {
160
252
 
161
253
  /*
162
254
  checking if the last item transcript matches with current uid
163
- If yes then updating the last transcript msg with current text
164
- If no then adding a new entry in the transcript
255
+ If yes then updating the last transcript msg with current text and translations
256
+ If no and we have new text, then adding a new entry in the transcript
257
+ If no new text and no matching uid, don't add empty item
165
258
  */
166
259
  if (lastTranscript && lastTranscript.uid === textstream.uid) {
260
+ // Update existing transcript item (text + translations or just translations)
167
261
  const updatedTranscript = {
168
262
  ...lastTranscript,
169
263
  //text: lastTranscript.text + ' ' + currentFinalText, // missing few updates with reading prev values
170
- text: finalTranscriptList[textstream.uid].join(' '),
264
+ text: currentFinalText.length
265
+ ? finalTranscriptList[textstream.uid].join(' ')
266
+ : lastTranscript.text, // Keep existing text if no new text
267
+ translations: finalTranslationsForTranscript,
268
+ // preserve the original translation language from when this transcript was created
269
+ selectedTranslationLanguage:
270
+ lastTranscript.selectedTranslationLanguage,
171
271
  };
172
272
 
173
273
  return [
174
274
  ...prevTranscript.slice(0, lastTranscriptIndex),
175
275
  updatedTranscript,
176
276
  ];
177
- } else {
277
+ } else if (currentFinalText.length) {
278
+ // Only create new item if we have new text
178
279
  finalTranscriptList[textstream.uid] = [currentFinalText];
179
280
 
180
281
  return [
@@ -183,8 +284,19 @@ const useStreamMessageUtils = (): {
183
284
  uid: textstream.uid,
184
285
  time: new Date().getTime(),
185
286
  text: currentFinalText,
287
+ translations: finalTranslationsForTranscript,
288
+ // Store the current translation language with this transcript item
289
+ // This preserves which translation was active when this text was spoken
290
+ selectedTranslationLanguage:
291
+ selectedTranslationLanguageRef.current,
186
292
  },
187
293
  ];
294
+ } else {
295
+ // No new text and uid doesn't match - don't modify transcript
296
+ // console.log(
297
+ // '[TRANSCRIPT_DEBUG] Skipping transcript update - no new text and uid mismatch',
298
+ // );
299
+ return prevTranscript;
188
300
  }
189
301
  });
190
302
  }
@@ -197,22 +309,41 @@ const useStreamMessageUtils = (): {
197
309
  ? ''
198
310
  : finalList[textstream.uid]?.join(' ');
199
311
  const latestString = nonFinalText;
200
- const captionText =
201
- existingStringBuffer.length > 0
202
- ? existingStringBuffer + ' ' + latestString
203
- : latestString;
204
-
205
- // updating the captions
206
- captionText &&
207
- setCaptionObj(prevState => {
208
- return {
209
- ...prevState,
210
- [textstream.uid]: {
211
- text: captionText,
212
- lastUpdated: new Date().getTime(),
213
- },
214
- };
215
- });
312
+ const captionText = isInterjecting
313
+ ? latestString
314
+ : existingStringBuffer.length > 0
315
+ ? existingStringBuffer + ' ' + latestString
316
+ : latestString;
317
+
318
+ // updating the captions with both transcription and translations
319
+ setCaptionObj(prevState => {
320
+ const existingTranslations =
321
+ prevState[textstream.uid]?.translations || [];
322
+
323
+ // Update existing translations or add new ones
324
+ const updatedTranslations = [...existingTranslations];
325
+
326
+ for (const newTrans of translations) {
327
+ const existingIndex = updatedTranslations.findIndex(
328
+ t => t.lang === newTrans.lang,
329
+ );
330
+
331
+ if (existingIndex >= 0) {
332
+ updatedTranslations[existingIndex] = newTrans;
333
+ } else {
334
+ updatedTranslations.push(newTrans);
335
+ }
336
+ }
337
+
338
+ return {
339
+ ...prevState,
340
+ [textstream.uid]: {
341
+ text: captionText || prevState[textstream.uid]?.text || '',
342
+ translations: updatedTranslations,
343
+ lastUpdated: new Date().getTime(),
344
+ },
345
+ };
346
+ });
216
347
 
217
348
  // console.group('STT-logs');
218
349
  // console.log('Recived uid =>', textstream.uid);
@@ -1,5 +1,5 @@
1
1
  import {ContentObjects} from '../../../agora-rn-uikit/src/Contexts/RtcContext';
2
- import {TranscriptItem} from './useCaption';
2
+ import {TranscriptItem, LanguageTranslationConfig} from './useCaption';
3
3
 
4
4
  export function formatTime(timestamp: number): string {
5
5
  const d = new Date(timestamp);
@@ -11,6 +11,26 @@ export function formatTime(timestamp: number): string {
11
11
  return `${H}:${m} ${suffix}`;
12
12
  }
13
13
 
14
+ /**
15
+ * Check if translation configuration has changed
16
+ * Compares source language and target languages between two configs
17
+ * @param prev - Previous translation configuration
18
+ * @param next - New translation configuration
19
+ * @returns true if config has changed, false otherwise
20
+ */
21
+ export const hasConfigChanged = (
22
+ prev: LanguageTranslationConfig,
23
+ next: LanguageTranslationConfig,
24
+ ): boolean => {
25
+ const sourceChanged =
26
+ (prev.source || []).sort().join(',') !==
27
+ (next.source || []).sort().join(',');
28
+ const targetsChanged =
29
+ (prev.targets || []).sort().join(',') !==
30
+ (next.targets || []).sort().join(',');
31
+ return sourceChanged || targetsChanged;
32
+ };
33
+
14
34
  export type LanguageType =
15
35
  | 'ar-EG'
16
36
  | 'ar-JO'
@@ -118,14 +138,56 @@ export const formatTranscriptContent = (
118
138
  meetingTitle: string,
119
139
  defaultContent: ContentObjects,
120
140
  ) => {
141
+ // Helper function to get display text based on stored translation language
142
+ // Uses the translation language that was active when this transcript item was created
143
+ const getDisplayText = (item: TranscriptItem): string => {
144
+ // Use the stored translation language from when this item was created
145
+ const storedTranslationLanguage = item.selectedTranslationLanguage;
146
+
147
+ if (!storedTranslationLanguage || !item.translations) {
148
+ return item.text; // no translation selected or no translations available, show original
149
+ }
150
+
151
+ // find translation for the stored language
152
+ const currentTranslation = item.translations.find(
153
+ t => t.lang === storedTranslationLanguage,
154
+ );
155
+ if (currentTranslation?.text) {
156
+ return currentTranslation.text;
157
+ }
158
+
159
+ // if stored language not available, show original
160
+ return item.text;
161
+ };
162
+
121
163
  const formattedContent = meetingTranscript
122
164
  .map(item => {
123
- if (item.uid.toString().indexOf('langUpdate') !== -1) {
165
+ if (
166
+ item.uid.toString().indexOf('langUpdate') !== -1 ||
167
+ item.uid.toString().indexOf('translationUpdate') !== -1
168
+ ) {
124
169
  return `${defaultContent[item?.uid?.split('-')[1]]?.name} ${item.text}`;
125
170
  }
126
- return `${defaultContent[item.uid].name} ${formatTime(
171
+
172
+ // Build transcript entry with original text and all translations
173
+ let transcriptEntry = `${defaultContent[item.uid]?.name} ${formatTime(
127
174
  Number(item?.time),
128
175
  )}:\n${item.text}`;
176
+
177
+ // Add all translations with language labels
178
+ if (item.translations && item.translations.length > 0) {
179
+ const translationLines = item.translations
180
+ .map(trans => {
181
+ const langLabel =
182
+ langData.find(l => l.value === trans.lang)?.label || trans.lang;
183
+ return `${langLabel}: ${trans.text}`;
184
+ })
185
+ .join('\n');
186
+
187
+ transcriptEntry += `\n${translationLines}`;
188
+ }
189
+
190
+ return transcriptEntry;
129
191
  })
130
192
  .join('\n\n');
131
193
 
@@ -160,3 +222,109 @@ export const formatTranscriptContent = (
160
222
 
161
223
  return [finalContent, fileName];
162
224
  };
225
+
226
+ /**
227
+ * Get the appropriate caption text to display based on the user's source/spoken language
228
+ * For other users' captions: shows translation matching user's source/spoken language
229
+ * For current user's captions: shows original text
230
+ * Falls back to original text if translation is not available
231
+ *
232
+ * @param captionText - The original caption text
233
+ * @param translations - Array of available translations
234
+ * @param viewerSourceLanguage - The user's source (spoken) language
235
+ * @param speakerUid - The UID of the person speaking
236
+ * @param currentUserUid - The UID of the current user
237
+ * @returns The appropriate caption text to display
238
+ */
239
+ export const getUserTranslatedText = (
240
+ captionText: string,
241
+ translations: Array<{lang: string; text: string; isFinal: boolean}> = [],
242
+ viewerSourceLanguage: LanguageType,
243
+ speakerUid: string | number,
244
+ currentUserUid: string | number,
245
+ ): {
246
+ value: string;
247
+ langCode: string;
248
+ } => {
249
+ // console.log(
250
+ // 'getUserTranslatedText input params',
251
+ // captionText,
252
+ // translations,
253
+ // viewerSourceLanguage,
254
+ // speakerUid,
255
+ // currentUserUid,
256
+ // );
257
+
258
+ // 1. If the speaker is the local user, always show their own source text
259
+ if (speakerUid === currentUserUid) {
260
+ return {
261
+ value: captionText,
262
+ langCode: getLanguageLabel([viewerSourceLanguage]) || '',
263
+ };
264
+ }
265
+ // For other users' captions, try to find translation matching viewer's source language
266
+ if (viewerSourceLanguage && translations && translations.length > 0) {
267
+ const matchingTranslation = translations.find(
268
+ t => t.lang === viewerSourceLanguage,
269
+ );
270
+ if (matchingTranslation) {
271
+ // Translation exists (even if empty)
272
+ // - If text is empty: show nothing (don’t fallback)
273
+ // - If text exists: show it
274
+ const translatedText = matchingTranslation.text?.trim() || '';
275
+ return {
276
+ value: translatedText,
277
+ langCode: getLanguageLabel([matchingTranslation.lang]) || '',
278
+ };
279
+ }
280
+ }
281
+
282
+ // Fallback to original text if no translation found
283
+ return {
284
+ value: captionText,
285
+ langCode: 'Original',
286
+ };
287
+ };
288
+
289
+ export interface TranslateConfig {
290
+ source_lang: string;
291
+ target_lang: string[];
292
+ }
293
+
294
+ export const mergeTranslationConfigs = (
295
+ existingTranslateConfig: TranslateConfig[],
296
+ userOwnLanguages: LanguageType[],
297
+ selectedTranslationLanguage: string,
298
+ ): TranslateConfig[] => {
299
+ // Create new translate_config for user's own languages
300
+ const newTranslateConfigs = userOwnLanguages.map(spokenLang => ({
301
+ source_lang: spokenLang,
302
+ target_lang: [selectedTranslationLanguage],
303
+ }));
304
+
305
+ // Merge with existing configuration
306
+ const mergedTranslateConfig = [...existingTranslateConfig];
307
+
308
+ newTranslateConfigs.forEach(newConfig => {
309
+ const existingIndex = mergedTranslateConfig.findIndex(
310
+ existing => existing.source_lang === newConfig.source_lang,
311
+ );
312
+
313
+ if (existingIndex !== -1) {
314
+ // Same source language - merge target languages
315
+ const existingTargets = mergedTranslateConfig[existingIndex].target_lang;
316
+ const mergedTargets = [
317
+ ...new Set([...existingTargets, ...newConfig.target_lang]),
318
+ ];
319
+ mergedTranslateConfig[existingIndex] = {
320
+ ...mergedTranslateConfig[existingIndex],
321
+ target_lang: mergedTargets,
322
+ };
323
+ } else {
324
+ // Different source language - add new config
325
+ mergedTranslateConfig.push(newConfig);
326
+ }
327
+ });
328
+
329
+ return mergedTranslateConfig;
330
+ };
@@ -71,7 +71,6 @@ export const handleChatSend = ({
71
71
  return;
72
72
  }
73
73
 
74
- // Text message sent update sender side ui
75
74
  const sendTextMessage = () => {
76
75
  const option = {
77
76
  chatType: selectedUserId
@@ -25,7 +25,6 @@ import {
25
25
  toolbarItemShareText,
26
26
  } from '../../language/default-labels/videoCallScreenLabels';
27
27
  import {useToolbarProps} from '../../atoms/ToolbarItem';
28
- import {useBreakoutRoom} from '../../components/breakout-room/context/BreakoutRoomContext';
29
28
  /**
30
29
  * A component to start and stop screen sharing on web clients.
31
30
  * Screen sharing is not yet implemented on mobile platforms.
@@ -54,11 +53,6 @@ const ScreenshareButton = (props: ScreenshareButtonProps) => {
54
53
  const {setShowStartScreenSharePopup} = useVideoCall();
55
54
  const screenShareButtonLabel = useString<boolean>(toolbarItemShareText);
56
55
  const lstooltip = useString<boolean>(livestreamingShareTooltipText);
57
- const {permissions} = useBreakoutRoom();
58
- // In the main room (default case), permissions come from the main room state.
59
- // If the user is in a breakout room, retrieve permissions from the breakout room instead.
60
- const canScreenshareInBreakoutRoom = permissions.canScreenshare;
61
-
62
56
  const onPress = () => {
63
57
  if (isScreenshareActive) {
64
58
  stopScreenshare();
@@ -111,16 +105,6 @@ const ScreenshareButton = (props: ScreenshareButtonProps) => {
111
105
  iconButtonProps.disabled = true;
112
106
  }
113
107
 
114
- if (!canScreenshareInBreakoutRoom) {
115
- iconButtonProps.iconProps = {
116
- ...iconButtonProps.iconProps,
117
- tintColor: $config.SEMANTIC_NEUTRAL,
118
- showWarningIcon: true,
119
- };
120
- iconButtonProps.toolTipMessage = 'cannot screenshare';
121
- iconButtonProps.disabled = true;
122
- }
123
-
124
108
  return props?.render ? (
125
109
  props.render(onPress, isScreenshareActive)
126
110
  ) : isToolbarMenuItem ? (
@@ -319,7 +319,7 @@ export const ScreenshareConfigure = (props: {children: React.ReactNode}) => {
319
319
  'Trying to start native screenshare',
320
320
  );
321
321
  if (video) {
322
- await localMute(MUTE_LOCAL_TYPE.video);
322
+ localMute(MUTE_LOCAL_TYPE.video);
323
323
  }
324
324
  try {
325
325
  await engine.current.startScreenCapture({
@@ -14,14 +14,12 @@ import {
14
14
  peoplePanelWaitingRoomRequestApprovalBtnTxt,
15
15
  peoplePanelWaitingRoomRequestDenyBtnTxt,
16
16
  } from '../../../src/language/default-labels/videoCallScreenLabels';
17
- import ChatContext from '../../components/ChatContext';
18
17
 
19
18
  const WaitingRoomButton = props => {
20
19
  const {uid, screenUid, isAccept} = props;
21
20
  const {approval} = useWaitingRoomAPI();
22
21
  const localUid = useLocalUid();
23
22
  const {dispatch} = useContext(DispatchContext);
24
- const {syncUserState} = useContext(ChatContext);
25
23
  const {waitingRoomRef} = useWaitingRoomContext();
26
24
  const admintext = useString(peoplePanelWaitingRoomRequestApprovalBtnTxt)();
27
25
  const denytext = useString(peoplePanelWaitingRoomRequestDenyBtnTxt)();
@@ -42,11 +40,10 @@ const WaitingRoomButton = props => {
42
40
  Toast.hide();
43
41
  }
44
42
 
45
- // dispatch({
46
- // type: 'UpdateRenderList',
47
- // value: [uid, {isInWaitingRoom: false}],
48
- // });
49
- syncUserState(uid, {isInWaitingRoom: false});
43
+ dispatch({
44
+ type: 'UpdateRenderList',
45
+ value: [uid, {isInWaitingRoom: false}],
46
+ });
50
47
 
51
48
  if (waitingRoomRef.current) {
52
49
  waitingRoomRef.current[uid] = approved ? 'APPROVED' : 'REJECTED';
@@ -51,6 +51,9 @@ export interface userEventsMapInterface {
51
51
  'token-refreshed': () => void;
52
52
  'rtc-user-removed': (uid: UidType, channel: string) => void;
53
53
  unauthorized: (errorMessage) => void;
54
+ 'waiting-room-approval-requested': () => void;
55
+ 'waiting-room-approval-granted': () => void;
56
+ 'waiting-room-approval-rejected': () => void;
54
57
  }
55
58
 
56
59
  const SDKEvents = createNanoEvents<userEventsMapInterface>();
@@ -4,11 +4,11 @@ import {
4
4
  useCaption,
5
5
  useContent,
6
6
  useRoomInfo,
7
- useSTTAPI,
8
7
  } from 'customization-api';
9
8
  import {PropsContext, DispatchContext} from '../../agora-rn-uikit';
10
9
  import {useHistory} from '../components/Router';
11
10
  import {stopForegroundService} from '../subComponents/LocalEndCall';
11
+ import RTMEngine from '../rtm/RTMEngine';
12
12
  import {ENABLE_AUTH} from '../auth/config';
13
13
  import {useAuth} from '../auth/AuthProvider';
14
14
  import {useChatConfigure} from '../components/chat/chatConfigure';
@@ -16,13 +16,12 @@ import {useChatConfigure} from '../components/chat/chatConfigure';
16
16
  const useEndCall = () => {
17
17
  const history = useHistory();
18
18
  const {defaultContent} = useContent();
19
- const {isSTTActive} = useCaption();
19
+ const {isSTTActive, stopSTTBotSession} = useCaption();
20
20
  const {
21
21
  data: {isHost},
22
22
  } = useRoomInfo();
23
23
  const {authLogin} = useAuth();
24
24
  const {deleteChatUser} = useChatConfigure();
25
- const {stop: stopSTTAPI} = useSTTAPI();
26
25
 
27
26
  const {rtcProps} = useContext(PropsContext);
28
27
  const {dispatch} = useContext(DispatchContext);
@@ -59,7 +58,7 @@ const useEndCall = () => {
59
58
  );
60
59
  if (usersInCall.length === 1 && isSTTActive) {
61
60
  console.log('Stopping stt api as only one host is in the call');
62
- stopSTTAPI().catch(error => {
61
+ stopSTTBotSession().catch(error => {
63
62
  console.log('Error stopping stt', error);
64
63
  });
65
64
  }
@@ -68,6 +67,7 @@ const useEndCall = () => {
68
67
  if ($config.CHAT) {
69
68
  deleteChatUser();
70
69
  }
70
+ RTMEngine.getInstance().engine.leaveChannel(rtcProps.channel);
71
71
  if (!ENABLE_AUTH) {
72
72
  // await authLogout();
73
73
  await authLogin();
@@ -86,16 +86,14 @@ function useMuteToggleLocal() {
86
86
  );
87
87
 
88
88
  // Enable UI
89
- const newAudioState =
90
- localAudioState === ToggleState.enabled
91
- ? ToggleState.disabled
92
- : ToggleState.enabled;
93
-
94
89
  dispatch({
95
90
  type: 'LocalMuteAudio',
96
- value: [newAudioState],
91
+ value: [
92
+ localAudioState === ToggleState.enabled
93
+ ? ToggleState.disabled
94
+ : ToggleState.enabled,
95
+ ],
97
96
  });
98
-
99
97
  handleQueue();
100
98
  } catch (e) {
101
99
  dispatch({
@@ -154,16 +152,14 @@ function useMuteToggleLocal() {
154
152
  );
155
153
  }
156
154
  // Enable UI
157
- const newVideoState =
158
- localVideoState === ToggleState.enabled
159
- ? ToggleState.disabled
160
- : ToggleState.enabled;
161
-
162
155
  dispatch({
163
156
  type: 'LocalMuteVideo',
164
- value: [newVideoState],
157
+ value: [
158
+ localVideoState === ToggleState.enabled
159
+ ? ToggleState.disabled
160
+ : ToggleState.enabled,
161
+ ],
165
162
  });
166
-
167
163
  handleQueue();
168
164
  } catch (e) {
169
165
  dispatch({