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,16 +1,48 @@
1
1
  import {createHook} from 'customization-implementation';
2
- import React from 'react';
3
- import {LanguageType} from './utils';
2
+ import React, {useContext} from 'react';
3
+ import {LanguageType, getLanguageLabel, hasConfigChanged} from './utils';
4
+ import useSTTAPI, {STTAPIResponse} from './useSTTAPI';
5
+ import {useLocalUid} from '../../../agora-rn-uikit';
6
+ import {logger, LogSource} from '../../logger/AppBuilderLogger';
7
+ import events, {PersistanceLevel} from '../../rtm-events-api';
8
+ import {EventNames} from '../../rtm-events';
9
+ import useGetName from '../../utils/useGetName';
10
+ import Toast from '../../../react-native-toast-message';
11
+ import {useString} from '../../utils/useString';
12
+ import {
13
+ sttStartError,
14
+ sttUpdateError,
15
+ } from '../../language/default-labels/videoCallScreenLabels';
16
+ import chatContext from '../../components/ChatContext';
17
+
18
+ type TranslationItem = {
19
+ lang: string;
20
+ text: string;
21
+ isFinal: boolean;
22
+ };
23
+
24
+ export type LanguageTranslationConfig = {
25
+ source: LanguageType[]; // 'en-US'
26
+ targets: LanguageType[]; // ['zh-CN', 'ja-JP']
27
+ autoPopulate?: boolean; // e.g. if auto-populated from others
28
+ };
29
+
30
+ export type CaptionViewMode = 'original-and-translated' | 'translated';
4
31
 
5
32
  export type TranscriptItem = {
6
33
  uid: string;
7
34
  time: number;
8
35
  text: string;
36
+ translations?: TranslationItem[];
37
+ // Stores which translation language was active when this transcript was created
38
+ // This preserves historical context when users switch translation languages mid-meeting
39
+ selectedTranslationLanguage?: string;
9
40
  };
10
41
 
11
42
  type CaptionObj = {
12
43
  [key: string]: {
13
44
  text: string;
45
+ translations: TranslationItem[];
14
46
  lastUpdated: number;
15
47
  };
16
48
  };
@@ -28,9 +60,20 @@ export const CaptionContext = React.createContext<{
28
60
  isSTTActive: boolean;
29
61
  setIsSTTActive: React.Dispatch<React.SetStateAction<boolean>>;
30
62
 
31
- // holds the language selection for stt
32
- language: LanguageType[];
33
- setLanguage: React.Dispatch<React.SetStateAction<LanguageType[]>>;
63
+ // holds the language selection for stt (deprecated - use sttForm instead)
64
+ // language: LanguageType[];
65
+ // setLanguage: React.Dispatch<React.SetStateAction<LanguageType[]>>;
66
+
67
+ translationConfig: LanguageTranslationConfig;
68
+ setTranslationConfig: React.Dispatch<
69
+ React.SetStateAction<LanguageTranslationConfig>
70
+ >;
71
+
72
+ captionViewMode: CaptionViewMode;
73
+ setCaptionViewMode: React.Dispatch<React.SetStateAction<CaptionViewMode>>;
74
+
75
+ transcriptViewMode: CaptionViewMode;
76
+ setTranscriptViewMode: React.Dispatch<React.SetStateAction<CaptionViewMode>>;
34
77
 
35
78
  // holds meeting transcript
36
79
  meetingTranscript: TranscriptItem[];
@@ -40,6 +83,12 @@ export const CaptionContext = React.createContext<{
40
83
  isLangChangeInProgress: boolean;
41
84
  setIsLangChangeInProgress: React.Dispatch<React.SetStateAction<boolean>>;
42
85
 
86
+ // holds status of translation language change process
87
+ isTranslationChangeInProgress: boolean;
88
+ setIsTranslationChangeInProgress: React.Dispatch<
89
+ React.SetStateAction<boolean>
90
+ >;
91
+
43
92
  // holds live captions
44
93
  captionObj: CaptionObj;
45
94
  setCaptionObj: React.Dispatch<React.SetStateAction<CaptionObj>>;
@@ -50,6 +99,34 @@ export const CaptionContext = React.createContext<{
50
99
 
51
100
  activeSpeakerRef: React.MutableRefObject<string>;
52
101
  prevSpeakerRef: React.MutableRefObject<string>;
102
+
103
+ selectedTranslationLanguage: string;
104
+ setSelectedTranslationLanguage: React.Dispatch<React.SetStateAction<string>>;
105
+ // Ref for translation language - prevents stale closures in callbacks
106
+ selectedTranslationLanguageRef: React.MutableRefObject<string>;
107
+ // Ref for translation config - prevents stale closures in callbacks
108
+ translationConfigRef: React.MutableRefObject<LanguageTranslationConfig>;
109
+
110
+ // Stores spoken languages of all remote users (userUid -> spoken language)
111
+ // Used to auto-populate target languages for new users
112
+ remoteSpokenLanguages: Record<string, LanguageType>;
113
+ setRemoteSpokenLanguages: React.Dispatch<
114
+ React.SetStateAction<Record<string, LanguageType>>
115
+ >;
116
+
117
+ handleTranslateConfigChange: (
118
+ inputTranslationConfig: LanguageTranslationConfig,
119
+ ) => Promise<void>;
120
+ startSTTBotSession: (
121
+ newConfig: LanguageTranslationConfig,
122
+ ) => Promise<STTAPIResponse>;
123
+ updateSTTBotSession: (
124
+ newConfig: LanguageTranslationConfig,
125
+ ) => Promise<STTAPIResponse>;
126
+ stopSTTBotSession: () => Promise<void>;
127
+
128
+ // Helper function to get user UID from bot UID
129
+ getBotOwnerUid: (botUid: string | number) => string | number;
53
130
  }>({
54
131
  isCaptionON: false,
55
132
  setIsCaptionON: () => {},
@@ -57,22 +134,44 @@ export const CaptionContext = React.createContext<{
57
134
  setIsSTTError: () => {},
58
135
  isSTTActive: false,
59
136
  setIsSTTActive: () => {},
60
- language: ['en-US'],
61
- setLanguage: () => {},
137
+ // language: ['en-US'],
138
+ // setLanguage: () => {},
139
+ translationConfig: {
140
+ source: [],
141
+ targets: [],
142
+ },
143
+ setTranslationConfig: () => {},
144
+ captionViewMode: 'translated',
145
+ setCaptionViewMode: () => {},
146
+ transcriptViewMode: 'translated',
147
+ setTranscriptViewMode: () => {},
62
148
  meetingTranscript: [],
63
149
  setMeetingTranscript: () => {},
64
150
  isLangChangeInProgress: false,
65
151
  setIsLangChangeInProgress: () => {},
152
+ isTranslationChangeInProgress: false,
153
+ setIsTranslationChangeInProgress: () => {},
66
154
  captionObj: {},
67
155
  setCaptionObj: () => {},
68
156
  isSTTListenerAdded: false,
69
157
  setIsSTTListenerAdded: () => {},
70
158
  activeSpeakerRef: {current: ''},
71
159
  prevSpeakerRef: {current: ''},
160
+ selectedTranslationLanguage: '',
161
+ setSelectedTranslationLanguage: () => {},
162
+ selectedTranslationLanguageRef: {current: ''},
163
+ translationConfigRef: {current: {source: [], targets: []}},
164
+ remoteSpokenLanguages: {},
165
+ setRemoteSpokenLanguages: () => {},
166
+ handleTranslateConfigChange: async () => {},
167
+ startSTTBotSession: async () => ({success: false}),
168
+ updateSTTBotSession: async () => ({success: false}),
169
+ stopSTTBotSession: async () => {},
170
+ getBotOwnerUid: (botUid: string | number) => botUid,
72
171
  });
73
172
 
74
173
  interface CaptionProviderProps {
75
- callActive?: boolean;
174
+ callActive: boolean;
76
175
  children: React.ReactNode;
77
176
  }
78
177
 
@@ -83,21 +182,648 @@ const CaptionProvider: React.FC<CaptionProviderProps> = ({
83
182
  const [isSTTError, setIsSTTError] = React.useState<boolean>(false);
84
183
  const [isCaptionON, setIsCaptionON] = React.useState<boolean>(false);
85
184
  const [isSTTActive, setIsSTTActive] = React.useState<boolean>(false);
86
- const [language, setLanguage] = React.useState<[LanguageType]>(['']);
185
+ // const [language, setLanguage] = React.useState<[LanguageType]>(['']);
186
+
187
+ // STT Form state - contains agentId, source, and target languages
188
+ const [translationConfig, setTranslationConfig] =
189
+ React.useState<LanguageTranslationConfig>({
190
+ source: [],
191
+ targets: [],
192
+ });
193
+
194
+ const [captionViewMode, setCaptionViewMode] =
195
+ React.useState<CaptionViewMode>('translated');
196
+
197
+ const [transcriptViewMode, setTranscriptViewMode] =
198
+ React.useState<CaptionViewMode>('translated');
199
+
87
200
  const [isLangChangeInProgress, setIsLangChangeInProgress] =
88
201
  React.useState<boolean>(false);
202
+ const [isTranslationChangeInProgress, setIsTranslationChangeInProgress] =
203
+ React.useState<boolean>(false);
89
204
  const [meetingTranscript, setMeetingTranscript] = React.useState<
90
205
  TranscriptItem[]
91
206
  >([]);
92
207
  const [captionObj, setCaptionObj] = React.useState<CaptionObj>({});
208
+ console.log('[STT_PER_USER_BOT] captionObj: ', captionObj);
93
209
  const [isSTTListenerAdded, setIsSTTListenerAdded] =
94
210
  React.useState<boolean>(false);
95
211
  const [activeSpeakerUID, setActiveSpeakerUID] = React.useState<string>('');
96
212
  const [prevActiveSpeakerUID, setPrevActiveSpeakerUID] =
97
213
  React.useState<string>('');
214
+ const [selectedTranslationLanguage, setSelectedTranslationLanguage] =
215
+ React.useState<string>('');
216
+ const [remoteSpokenLanguages, setRemoteSpokenLanguages] = React.useState<
217
+ Record<string, LanguageType>
218
+ >({});
98
219
 
99
220
  const activeSpeakerRef = React.useRef('');
100
221
  const prevSpeakerRef = React.useRef('');
222
+ const selectedTranslationLanguageRef = React.useRef('');
223
+ const translationConfigRef = React.useRef<LanguageTranslationConfig>({
224
+ source: [],
225
+ targets: [],
226
+ });
227
+
228
+ // Sync ref with state for selectedTranslationLanguage
229
+ React.useEffect(() => {
230
+ selectedTranslationLanguageRef.current = selectedTranslationLanguage;
231
+ }, [selectedTranslationLanguage]);
232
+
233
+ // Sync ref with state for translationConfig
234
+ React.useEffect(() => {
235
+ translationConfigRef.current = translationConfig;
236
+ }, [translationConfig]);
237
+
238
+ // Import STT API methods
239
+ const {start, stop, update} = useSTTAPI();
240
+
241
+ const localUid = useLocalUid();
242
+ const username = useGetName();
243
+ const {hasUserJoinedRTM} = useContext(chatContext);
244
+
245
+ const [localBotUid, setLocalBotUid] = React.useState<number | null>(null);
246
+
247
+ // i18n labels for error toasts
248
+ const startErrorLabel = useString(sttStartError)();
249
+ const updateErrorLabel = useString(sttUpdateError)();
250
+
251
+ React.useEffect(() => {
252
+ if (!localUid || !username || !hasUserJoinedRTM) {
253
+ return;
254
+ } // wait for room info to be ready
255
+ const uid = generateBotUidForUser(localUid);
256
+ setLocalBotUid(uid);
257
+ }, [localUid, username, hasUserJoinedRTM]);
258
+
259
+ // Silent update function to update local user's STT config without showing progress bar
260
+ const silentUpdateSTT = React.useCallback(
261
+ async (newtargetLanguages: LanguageType[]) => {
262
+ try {
263
+ // Merge new target languages with existing ones and keep unique
264
+ const currentTargets = translationConfigRef.current?.targets || [];
265
+ const mergedTargets = Array.from(
266
+ new Set([...newtargetLanguages, ...currentTargets]),
267
+ );
268
+
269
+ const newConfig: LanguageTranslationConfig = {
270
+ source: translationConfigRef.current?.source || [],
271
+ targets: mergedTargets,
272
+ };
273
+
274
+ console.log(
275
+ '[STT_PER_USER_BOT] Silent updating with merged targets:',
276
+ 'newRemoteLanguages:',
277
+ newtargetLanguages,
278
+ 'existingTargets:',
279
+ currentTargets,
280
+ 'mergedTargets:',
281
+ mergedTargets,
282
+ );
283
+
284
+ // Call update API without showing progress bar (don't set isLangChangeInProgress)
285
+ const result = await update(localBotUid, newConfig);
286
+
287
+ if (result.success) {
288
+ setTranslationConfig(newConfig);
289
+ setIsSTTError(false);
290
+
291
+ logger.log(
292
+ LogSource.NetworkRest,
293
+ 'stt',
294
+ 'Local user STT updated silently',
295
+ {newtargetLanguages, mergedTargets, botUid: localBotUid},
296
+ );
297
+ } else {
298
+ setIsSTTError(true);
299
+ logger.error(
300
+ LogSource.NetworkRest,
301
+ 'stt',
302
+ 'Failed to silently update local user STT',
303
+ result.error,
304
+ );
305
+ }
306
+ } catch (error) {
307
+ setIsSTTError(true);
308
+ logger.error(
309
+ LogSource.NetworkRest,
310
+ 'stt',
311
+ 'Error in silentUpdateSTT',
312
+ error,
313
+ );
314
+ }
315
+ },
316
+ [localBotUid],
317
+ );
318
+
319
+ React.useEffect(() => {
320
+ const remoteLangs = Array.from(
321
+ new Set(
322
+ Object.entries(remoteSpokenLanguages)
323
+ .filter(([uid, lang]) => uid !== String(localUid) && lang)
324
+ .map(([, lang]) => lang),
325
+ ),
326
+ );
327
+
328
+ // If STT is active, check if received language differs from current target languages
329
+ if (isSTTActive && remoteLangs && remoteLangs.length > 0) {
330
+ const currentTargetLanguages =
331
+ translationConfigRef.current?.targets || [];
332
+ // Only update if any received language is not in current target languages
333
+ const hasTargetsChanged = remoteLangs.some(
334
+ lang => !currentTargetLanguages.includes(lang),
335
+ );
336
+ if (hasTargetsChanged) {
337
+ console.log(
338
+ '[STT_PER_USER_BOT] Spoken language change detected',
339
+ 'currentTargets:',
340
+ currentTargetLanguages,
341
+ 'receivedSpokenLanguages (unique):',
342
+ remoteLangs,
343
+ );
344
+ // Call silentUpdateSTT directly
345
+ silentUpdateSTT(remoteLangs);
346
+ }
347
+ }
348
+ }, [remoteSpokenLanguages]);
349
+
350
+ // Listen for spoken language updates from other users
351
+ React.useEffect(() => {
352
+ const handleSpokenLanguage = (data: any) => {
353
+ try {
354
+ const payload = JSON.parse(data.payload);
355
+ const {userUid, spokenLanguage, username} = payload;
356
+
357
+ console.log(
358
+ '[STT_PER_USER_BOT] Received spoken language from user:',
359
+ username,
360
+ 'userUid:',
361
+ userUid,
362
+ 'spokenLanguage:',
363
+ spokenLanguage,
364
+ );
365
+
366
+ // Update remoteSpokenLanguages with the user's spoken language
367
+ setRemoteSpokenLanguages(prev => ({
368
+ ...prev,
369
+ [userUid]: spokenLanguage,
370
+ }));
371
+ } catch (error) {
372
+ logger.error(
373
+ LogSource.Internals,
374
+ 'STT',
375
+ 'Failed to parse STT_SPOKEN_LANGUAGE event',
376
+ error,
377
+ );
378
+ }
379
+ };
380
+
381
+ events.on(EventNames.STT_SPOKEN_LANGUAGE, handleSpokenLanguage);
382
+
383
+ // Cleanup listener on unmount
384
+ return () => {
385
+ events.off(EventNames.STT_SPOKEN_LANGUAGE, handleSpokenLanguage);
386
+ };
387
+ }, []);
388
+
389
+ // Listen for when remote users stop translation and clear their translations
390
+ React.useEffect(() => {
391
+ const handleUserStoppedTranslation = (data: any) => {
392
+ try {
393
+ const payload = JSON.parse(data.payload);
394
+ const {userUid, username} = payload;
395
+
396
+ console.log(
397
+ '[STT_PER_USER_BOT] User stopped translation:',
398
+ username,
399
+ 'userUid:',
400
+ userUid,
401
+ );
402
+
403
+ // Clear translations for this user by setting captionObj translations to empty
404
+ setCaptionObj(prevState => {
405
+ if (prevState[userUid]) {
406
+ return {
407
+ ...prevState,
408
+ [userUid]: {
409
+ ...prevState[userUid],
410
+ translations: [], // Clear translations
411
+ },
412
+ };
413
+ }
414
+ return prevState;
415
+ });
416
+ } catch (error) {
417
+ logger.error(
418
+ LogSource.Internals,
419
+ 'STT',
420
+ 'Failed to parse USER_STOPPED_TRANSLATION event',
421
+ error,
422
+ );
423
+ }
424
+ };
425
+
426
+ events.on(
427
+ EventNames.USER_STOPPED_TRANSLATION,
428
+ handleUserStoppedTranslation,
429
+ );
430
+
431
+ // Cleanup listener on unmount
432
+ return () => {
433
+ events.off(
434
+ EventNames.USER_STOPPED_TRANSLATION,
435
+ handleUserStoppedTranslation,
436
+ );
437
+ };
438
+ }, []);
439
+
440
+ const startSTTBotSession = async (
441
+ newConfig: LanguageTranslationConfig,
442
+ ): Promise<STTAPIResponse> => {
443
+ if (!localBotUid || !localUid) {
444
+ console.warn('[STT] Missing localUid or botUid');
445
+ return {
446
+ success: false,
447
+ error: {message: 'Missing localUid or botUid'},
448
+ };
449
+ }
450
+
451
+ try {
452
+ setIsLangChangeInProgress(true);
453
+ const result = await start(localBotUid, newConfig);
454
+ console.log('STT start result: ', result);
455
+
456
+ if (result.success || result.error?.code === 610) {
457
+ // Success or already started
458
+ setIsSTTActive(true);
459
+ setIsSTTError(false);
460
+
461
+ // Add transcript entry for language change
462
+ // If STT was not active before, this is the first time setting the language
463
+ const actionText = !isSTTActive
464
+ ? `has set the spoken language to "${getLanguageLabel(
465
+ newConfig.source,
466
+ )}"`
467
+ : `changed the spoken language from "${getLanguageLabel(
468
+ translationConfig?.source,
469
+ )}" to "${getLanguageLabel(newConfig.source)}"`;
470
+
471
+ setTranslationConfig(newConfig);
472
+ setMeetingTranscript(prev => [
473
+ ...prev,
474
+ {
475
+ name: 'langUpdate',
476
+ time: new Date().getTime(),
477
+ uid: `langUpdate-${localUid}`,
478
+ text: actionText,
479
+ },
480
+ ]);
481
+
482
+ // Broadcast spoken language to all users
483
+ events.send(
484
+ EventNames.STT_SPOKEN_LANGUAGE,
485
+ JSON.stringify({
486
+ userUid: localUid,
487
+ spokenLanguage: newConfig.source[0],
488
+ username: username,
489
+ }),
490
+ PersistanceLevel.Sender,
491
+ );
492
+
493
+ logger.log(
494
+ LogSource.NetworkRest,
495
+ 'stt',
496
+ 'STT started successfully',
497
+ result.data,
498
+ );
499
+ } else {
500
+ // setIsCaptionON(false);
501
+ setIsSTTError(true);
502
+ logger.error(
503
+ LogSource.NetworkRest,
504
+ 'stt',
505
+ 'Failed to start STT',
506
+ result.error,
507
+ );
508
+ // Show error toast: text1 = translated label, text2 = API error
509
+ Toast.show({
510
+ leadingIconName: 'alert',
511
+ type: 'error',
512
+ text1: startErrorLabel,
513
+ text2: result.error?.message || 'Unknown error occurred',
514
+ visibilityTime: 4000,
515
+ });
516
+ }
517
+ setIsLangChangeInProgress(false);
518
+ return result;
519
+ } catch (error) {
520
+ setIsLangChangeInProgress(false);
521
+ setIsSTTError(true);
522
+ logger.error(LogSource.NetworkRest, 'stt', 'STT start error', error);
523
+ // Show error toast: text1 = translated label, text2 = exception error
524
+ Toast.show({
525
+ leadingIconName: 'alert',
526
+ type: 'error',
527
+ text1: startErrorLabel,
528
+ text2: error?.message || 'Unknown error occurred',
529
+ visibilityTime: 4000,
530
+ });
531
+ return {
532
+ success: false,
533
+ error: {message: error?.message || 'Unknown error'},
534
+ };
535
+ }
536
+ };
537
+
538
+ const updateSTTBotSession = async (
539
+ newConfig: LanguageTranslationConfig,
540
+ ): Promise<STTAPIResponse> => {
541
+ if (!localBotUid || !localUid) {
542
+ console.warn('[STT] Missing localUid or botUid');
543
+ return {
544
+ success: false,
545
+ error: {message: 'Missing localUid or botUid'},
546
+ };
547
+ }
548
+
549
+ try {
550
+ setIsLangChangeInProgress(true);
551
+ const result = await update(localBotUid, newConfig);
552
+
553
+ if (result.success) {
554
+ setTranslationConfig(newConfig);
555
+ setIsSTTError(false);
556
+
557
+ // Add transcript entry for language change
558
+ // Determine what actually changed
559
+ const spokenLanguageChanged =
560
+ translationConfig?.source[0] !== newConfig.source[0];
561
+ const oldTargetsSorted = (translationConfig?.targets || [])
562
+ .sort()
563
+ .map(lang => getLanguageLabel([lang]))
564
+ .join(', ');
565
+ const newTargetsSorted = (newConfig.targets || [])
566
+ .sort()
567
+ .map(lang => getLanguageLabel([lang]))
568
+ .join(', ');
569
+ const targetsChanged = oldTargetsSorted !== newTargetsSorted;
570
+
571
+ const oldTargetsLength = translationConfig?.targets?.length || 0;
572
+ const newTargetsLength = newConfig.targets?.length || 0;
573
+ const targetsWereDisabled =
574
+ oldTargetsLength > 0 && newTargetsLength === 0;
575
+ const targetsWereEnabled =
576
+ oldTargetsLength === 0 && newTargetsLength > 0;
577
+
578
+ // Build target message once
579
+ let targetMessage = '';
580
+ if (targetsChanged) {
581
+ if (targetsWereDisabled) {
582
+ targetMessage = 'stopped translations';
583
+ } else if (targetsWereEnabled) {
584
+ targetMessage = `enabled translations to "${newTargetsSorted}"`;
585
+ } else {
586
+ targetMessage = `changed target translation languages to "${newTargetsSorted}"`;
587
+ }
588
+ }
589
+
590
+ // Build action text
591
+ let actionText = '';
592
+ if (spokenLanguageChanged && targetsChanged) {
593
+ // Both spoken language and targets changed
594
+ actionText = `changed spoken language from "${getLanguageLabel(
595
+ translationConfig?.source,
596
+ )}" to "${getLanguageLabel(newConfig.source)}" and ${targetMessage}`;
597
+ } else if (spokenLanguageChanged) {
598
+ // Only spoken language changed
599
+ actionText = `changed the spoken language from "${getLanguageLabel(
600
+ translationConfig?.source,
601
+ )}" to "${getLanguageLabel(newConfig.source)}"`;
602
+ } else if (targetsChanged) {
603
+ // Only target languages changed
604
+ actionText = targetMessage;
605
+ }
606
+
607
+ if (actionText) {
608
+ setMeetingTranscript(prev => [
609
+ ...prev,
610
+ {
611
+ name: 'langUpdate',
612
+ uid: `langUpdate-${localUid}`,
613
+ time: new Date().getTime(),
614
+ text: actionText,
615
+ },
616
+ ]);
617
+ }
618
+
619
+ // Broadcast updated spoken language to all users (only if it changed)
620
+ if (spokenLanguageChanged) {
621
+ events.send(
622
+ EventNames.STT_SPOKEN_LANGUAGE,
623
+ JSON.stringify({
624
+ userUid: localUid,
625
+ spokenLanguage: newConfig.source[0],
626
+ username: username,
627
+ }),
628
+ PersistanceLevel.Sender,
629
+ );
630
+ }
631
+
632
+ // Send event when user stops translation
633
+ if (targetsWereDisabled) {
634
+ events.send(
635
+ EventNames.USER_STOPPED_TRANSLATION,
636
+ JSON.stringify({
637
+ userUid: localUid,
638
+ username: username,
639
+ }),
640
+ );
641
+ }
642
+
643
+ logger.log(
644
+ LogSource.NetworkRest,
645
+ 'stt',
646
+ 'STT updated successfully',
647
+ result.data,
648
+ );
649
+ } else {
650
+ setIsSTTError(true);
651
+ logger.error(
652
+ LogSource.NetworkRest,
653
+ 'stt',
654
+ 'Failed to update STT',
655
+ result.error,
656
+ );
657
+ // Show error toast: text1 = translated label, text2 = API error
658
+ Toast.show({
659
+ leadingIconName: 'alert',
660
+ type: 'error',
661
+ text1: updateErrorLabel,
662
+ text2: result.error?.message || 'Unknown error occurred',
663
+ visibilityTime: 4000,
664
+ });
665
+ }
666
+ setIsLangChangeInProgress(false);
667
+ return result;
668
+ } catch (error) {
669
+ setIsLangChangeInProgress(false);
670
+ setIsSTTError(true);
671
+ logger.error(LogSource.NetworkRest, 'stt', 'STT update error', error);
672
+ // Show error toast: text1 = translated label, text2 = exception error
673
+ Toast.show({
674
+ leadingIconName: 'alert',
675
+ type: 'error',
676
+ text1: updateErrorLabel,
677
+ text2: error?.message || 'Unknown error occurred',
678
+ visibilityTime: 4000,
679
+ });
680
+ return {
681
+ success: false,
682
+ error: {message: error?.message || 'Unknown error'},
683
+ };
684
+ }
685
+ };
686
+
687
+ const handleTranslateConfigChange = async (
688
+ inputTranslateConfig: LanguageTranslationConfig,
689
+ ) => {
690
+ if (!localBotUid || !localUid) {
691
+ console.warn('[STT] Missing localUid or botUid');
692
+ return;
693
+ }
694
+
695
+ const newConfig: LanguageTranslationConfig = {
696
+ source: inputTranslateConfig?.source,
697
+ targets: inputTranslateConfig?.targets,
698
+ };
699
+
700
+ let action: 'start' | 'update' = 'start';
701
+ if (!isSTTActive) {
702
+ action = 'start';
703
+ } else if (hasConfigChanged(translationConfig, newConfig)) {
704
+ action = 'update';
705
+ }
706
+
707
+ console.log('[STT_HANDLE_CONFIRM]', {
708
+ action,
709
+ localBotUid,
710
+ inputConfig: inputTranslateConfig,
711
+ sanitizedConfig: newConfig,
712
+ });
713
+
714
+ try {
715
+ switch (action) {
716
+ case 'start':
717
+ const startResult = await startSTTBotSession(newConfig);
718
+ if (!startResult.success) {
719
+ throw new Error(
720
+ startResult.error?.message || 'Failed to start STT',
721
+ );
722
+ }
723
+ break;
724
+
725
+ case 'update':
726
+ const updateResult = await updateSTTBotSession(newConfig);
727
+ if (!updateResult.success) {
728
+ throw new Error(
729
+ updateResult.error?.message || 'Failed to update STT config',
730
+ );
731
+ }
732
+ break;
733
+
734
+ default:
735
+ console.warn('Unknown STT action');
736
+ }
737
+ } catch (error) {
738
+ setIsSTTError(true);
739
+ setIsLangChangeInProgress(false);
740
+ logger.error(
741
+ LogSource.NetworkRest,
742
+ 'stt',
743
+ 'Error in handleTranslateConfigChange',
744
+ error,
745
+ );
746
+ throw error;
747
+ }
748
+ };
749
+
750
+ const stopSTTBotSession = React.useCallback(async () => {
751
+ console.log('[STT_PER_USER_BOT] stopCaption called');
752
+
753
+ if (!localBotUid) {
754
+ console.warn('[STT] Missing botUid');
755
+ return;
756
+ }
757
+
758
+ try {
759
+ const result = await stop(localBotUid);
760
+
761
+ if (result.success) {
762
+ // Set STT inactive locally
763
+ setIsSTTActive(false);
764
+ // Clear user's source language when stopping
765
+ setTranslationConfig({
766
+ source: [],
767
+ targets: [],
768
+ });
769
+ // setIsCaptionON(false);
770
+ setIsSTTError(false);
771
+
772
+ logger.log(
773
+ LogSource.NetworkRest,
774
+ 'stt',
775
+ 'STT stopped successfully',
776
+ result.data,
777
+ );
778
+ } else {
779
+ setIsSTTError(true);
780
+ logger.error(
781
+ LogSource.NetworkRest,
782
+ 'stt',
783
+ 'Failed to stop STT',
784
+ result.error,
785
+ );
786
+ }
787
+ } catch (error) {
788
+ setIsSTTError(true);
789
+ logger.error(
790
+ LogSource.NetworkRest,
791
+ 'stt',
792
+ 'Error in stopSTTBotSession',
793
+ error,
794
+ );
795
+ throw error;
796
+ }
797
+ }, [stop, localBotUid]);
798
+
799
+ // Helper function to convert user UID to stt bot UID
800
+ const generateBotUidForUser = (userLocalUid: number): number => {
801
+ return 900000000 + (userLocalUid % 100000000);
802
+ };
803
+
804
+ // Helper function to convert bot UID to user UID
805
+ // Bot UIDs are in format: 900000000 + userUid
806
+ // This function reverse-maps botUid → userUid using mathematical calculation
807
+ const getBotOwnerUid = React.useCallback(
808
+ (botUid: string | number): string | number => {
809
+ const botUidStr = String(botUid);
810
+
811
+ // Check if this is a special UID (langUpdate, translationUpdate, etc.)
812
+ if (botUidStr.indexOf('Update') !== -1) {
813
+ return botUid;
814
+ }
815
+
816
+ // If it's a bot UID (starts with 900000000), extract the user UID
817
+ const botUidNum = Number(botUid);
818
+ if (!isNaN(botUidNum) && botUidNum >= 900000000) {
819
+ return botUidNum - 900000000;
820
+ }
821
+
822
+ // Otherwise it's already a user UID, return as is
823
+ return botUid;
824
+ },
825
+ [],
826
+ );
101
827
 
102
828
  return (
103
829
  <CaptionContext.Provider
@@ -108,18 +834,35 @@ const CaptionProvider: React.FC<CaptionProviderProps> = ({
108
834
  setIsSTTError,
109
835
  isSTTActive,
110
836
  setIsSTTActive,
111
- language,
112
- setLanguage,
837
+ translationConfig,
838
+ setTranslationConfig,
839
+ captionViewMode,
840
+ setCaptionViewMode,
841
+ transcriptViewMode,
842
+ setTranscriptViewMode,
113
843
  meetingTranscript,
114
844
  setMeetingTranscript,
115
845
  isLangChangeInProgress,
116
846
  setIsLangChangeInProgress,
847
+ isTranslationChangeInProgress,
848
+ setIsTranslationChangeInProgress,
117
849
  captionObj,
118
850
  setCaptionObj,
119
851
  isSTTListenerAdded,
120
852
  setIsSTTListenerAdded,
121
853
  activeSpeakerRef,
122
854
  prevSpeakerRef,
855
+ selectedTranslationLanguage,
856
+ setSelectedTranslationLanguage,
857
+ selectedTranslationLanguageRef,
858
+ translationConfigRef,
859
+ remoteSpokenLanguages,
860
+ setRemoteSpokenLanguages,
861
+ handleTranslateConfigChange,
862
+ startSTTBotSession,
863
+ updateSTTBotSession,
864
+ stopSTTBotSession,
865
+ getBotOwnerUid,
123
866
  }}>
124
867
  {children}
125
868
  </CaptionContext.Provider>