agora-appbuilder-core 4.1.9 → 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 (58) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
  3. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
  4. package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
  5. package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
  6. package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
  7. package/template/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
  8. package/template/android/app/build.gradle +0 -7
  9. package/template/bridge/rtm/web/Types.ts +0 -183
  10. package/template/bridge/rtm/web/index.ts +491 -423
  11. package/template/defaultConfig.js +3 -3
  12. package/template/ios/Podfile +0 -41
  13. package/template/package.json +5 -5
  14. package/template/src/assets/font-styles.css +4 -0
  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 -0
  19. package/template/src/atoms/DropDownMulti.tsx +80 -29
  20. package/template/src/atoms/Input.tsx +2 -1
  21. package/template/src/components/Controls.tsx +148 -143
  22. package/template/src/components/EventsConfigure.tsx +152 -97
  23. package/template/src/components/RTMConfigure.tsx +426 -644
  24. package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
  25. package/template/src/components/precall/joinCallBtn.tsx +7 -2
  26. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +8 -3
  27. package/template/src/components/precall/joinWaitingRoomBtn.tsx +22 -4
  28. package/template/src/components/precall/textInput.tsx +45 -22
  29. package/template/src/components/precall/usePreCall.tsx +7 -0
  30. package/template/src/components/room-info/useRoomInfo.tsx +5 -0
  31. package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -4
  32. package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
  33. package/template/src/pages/video-call/SidePanelHeader.tsx +81 -36
  34. package/template/src/rtm/RTMEngine.ts +33 -130
  35. package/template/src/rtm-events/constants.ts +6 -0
  36. package/template/src/rtm-events-api/Events.ts +30 -106
  37. package/template/src/subComponents/caption/Caption.tsx +48 -7
  38. package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
  39. package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
  40. package/template/src/subComponents/caption/CaptionText.tsx +103 -2
  41. package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
  42. package/template/src/subComponents/caption/Transcript.tsx +46 -11
  43. package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
  44. package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
  45. package/template/src/subComponents/caption/proto/ptoto.js +38 -4
  46. package/template/src/subComponents/caption/proto/test.proto +34 -19
  47. package/template/src/subComponents/caption/useCaption.tsx +753 -10
  48. package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
  49. package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
  50. package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
  51. package/template/src/subComponents/caption/utils.ts +171 -3
  52. package/template/src/utils/SdkEvents.ts +3 -0
  53. package/template/src/utils/useEndCall.ts +3 -5
  54. package/template/src/utils/useSpeechToText.ts +31 -20
  55. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
  56. package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
  57. package/template/bridge/rtm/web/index-legacy.ts +0 -540
  58. package/template/src/components/RTMConfigure-legacy.tsx +0 -848
@@ -1,64 +1,97 @@
1
1
  import React, {useContext} from 'react';
2
2
  import StorageContext from '../../components/StorageContext';
3
3
  import {useRoomInfo} from '../../components/room-info/useRoomInfo';
4
- import {useCaption} from './useCaption';
5
- import events, {PersistanceLevel} from '../../rtm-events-api';
6
- import {EventNames} from '../../rtm-events';
7
- import {getLanguageLabel, LanguageType} from './utils';
8
- import useGetName from '../../utils/useGetName';
9
- import {capitalizeFirstLetter} from '../../utils/common';
4
+ import {LanguageTranslationConfig} from './useCaption';
10
5
  import {PropsContext, useLocalUid} from '../../../agora-rn-uikit';
11
6
  import {logger, LogSource} from '../../logger/AppBuilderLogger';
12
7
  import getUniqueID from '../../utils/getUniqueID';
13
8
 
9
+ export interface STTAPIResponse {
10
+ success: boolean;
11
+ data?: any;
12
+ error?: {
13
+ message: string;
14
+ code?: number;
15
+ };
16
+ }
17
+
14
18
  interface IuseSTTAPI {
15
- start: (lang: LanguageType[]) => Promise<{message: string} | null>;
16
- stop: () => Promise<void>;
17
- restart: (lang: LanguageType[]) => Promise<void>;
18
- isAuthorizedSTTUser: () => boolean;
19
- isAuthorizedTranscriptUser: () => boolean;
19
+ start: (
20
+ botUid: number,
21
+ translationConfig: LanguageTranslationConfig,
22
+ ) => Promise<STTAPIResponse>;
23
+ update: (
24
+ botUid: number,
25
+ translationConfig: LanguageTranslationConfig,
26
+ ) => Promise<STTAPIResponse>;
27
+ stop: (botUid: number) => Promise<STTAPIResponse>;
20
28
  }
21
29
 
22
30
  const useSTTAPI = (): IuseSTTAPI => {
23
31
  const {store} = React.useContext(StorageContext);
24
32
  const {
25
- data: {roomId, isHost},
33
+ data: {roomId},
26
34
  } = useRoomInfo();
27
- const {
28
- language,
29
- isSTTActive,
30
- setIsSTTActive,
31
- setIsLangChangeInProgress,
32
- setLanguage,
33
- setMeetingTranscript,
34
- setIsSTTError,
35
- } = useCaption();
36
-
37
- const currentLangRef = React.useRef<LanguageType[]>([]);
35
+ const {rtcProps} = useContext(PropsContext);
38
36
  const STT_API_URL = `${$config.BACKEND_ENDPOINT}/v1/stt`;
39
- const username = useGetName();
40
37
  const localUid = useLocalUid();
41
- const {rtcProps} = useContext(PropsContext);
42
-
43
- React.useEffect(() => {
44
- currentLangRef.current = language;
45
- }, [language]);
46
38
 
47
- const apiCall = async (method: string, lang: LanguageType[] = []) => {
39
+ const apiCall = async (
40
+ method: 'startv7' | 'update' | 'stopv7',
41
+ botUid: number,
42
+ translationConfig?: LanguageTranslationConfig,
43
+ ): Promise<STTAPIResponse> => {
48
44
  const requestId = getUniqueID();
49
45
  const startReqTs = Date.now();
50
- logger.log(
51
- LogSource.NetworkRest,
52
- 'stt',
53
- `Trying to ${method} stt for lang ${lang}`,
54
- {
55
- method,
56
- lang,
57
- requestId,
58
- startReqTs,
59
- },
60
- );
46
+
61
47
  try {
48
+ // Calculate which user this bot belongs to
49
+ const ownerUid = botUid - 900000000;
50
+
51
+ let requestBody: any = {
52
+ passphrase: roomId?.host || roomId?.attendee || '',
53
+ dataStream_uid: botUid,
54
+ encryption_mode: $config.ENCRYPTION_ENABLED
55
+ ? rtcProps.encryption.mode
56
+ : null,
57
+ };
58
+
59
+ console.log(
60
+ `[STT_BOT_SUBSCRIPTION] ${method.toUpperCase()} - Bot UID: ${botUid} will subscribe to User UID: ${ownerUid}`,
61
+ {
62
+ method,
63
+ botUid,
64
+ ownerUid,
65
+ translationConfig,
66
+ },
67
+ );
68
+ // Add translate_config only for start/update methods
69
+ if (translationConfig?.source?.[0]) {
70
+ requestBody.lang = translationConfig.source;
71
+ // Sanitize payload: remove source language from targets to avoid API errors
72
+ const sanitizedTargets =
73
+ translationConfig?.targets?.filter(
74
+ target => target !== translationConfig?.source[0],
75
+ ) || [];
76
+ const shouldTranslate = sanitizedTargets.length > 0;
77
+ // Add translate_config payload only if targets exist
78
+ if (shouldTranslate) {
79
+ requestBody.translate_config = [
80
+ {
81
+ source_lang: translationConfig.source[0],
82
+ target_lang: sanitizedTargets,
83
+ },
84
+ ];
85
+ if (method === 'update') {
86
+ requestBody.translate = true;
87
+ }
88
+ } else if (method === 'update') {
89
+ // If method is update and no targets are passed
90
+ requestBody.translate = false;
91
+ }
92
+ requestBody.subscribeAudioUids = [`${localUid}`];
93
+ }
94
+
62
95
  const response = await fetch(`${STT_API_URL}/${method}`, {
63
96
  method: 'POST',
64
97
  headers: {
@@ -67,22 +100,17 @@ const useSTTAPI = (): IuseSTTAPI => {
67
100
  'X-Request-Id': requestId,
68
101
  'X-Session-Id': logger.getSessionId(),
69
102
  },
70
- body: JSON.stringify({
71
- passphrase: roomId?.host || '',
72
- lang: lang,
73
- dataStream_uid: 111111, // bot ID
74
- encryption_mode: $config.ENCRYPTION_ENABLED
75
- ? rtcProps.encryption.mode
76
- : null,
77
- }),
103
+ body: JSON.stringify(requestBody),
78
104
  });
105
+
79
106
  const res = await response.json();
80
107
  const endReqTs = Date.now();
81
108
  const latency = endReqTs - startReqTs;
109
+
82
110
  logger.log(
83
111
  LogSource.NetworkRest,
84
112
  'stt',
85
- `STT API Success - Called ${method} on stt with lang ${lang}`,
113
+ `STT API Success - Called ${method}`,
86
114
  {
87
115
  responseData: res,
88
116
  requestId,
@@ -91,14 +119,30 @@ const useSTTAPI = (): IuseSTTAPI => {
91
119
  latency,
92
120
  },
93
121
  );
94
- return res;
122
+
123
+ // Check if response has error
124
+ if (res?.error?.message) {
125
+ return {
126
+ success: false,
127
+ error: {
128
+ message: res.error.message,
129
+ code: res.error.code,
130
+ },
131
+ data: res,
132
+ };
133
+ }
134
+
135
+ return {
136
+ success: true,
137
+ data: res,
138
+ };
95
139
  } catch (error) {
96
140
  const endReqTs = Date.now();
97
141
  const latency = endReqTs - startReqTs;
98
142
  logger.error(
99
143
  LogSource.NetworkRest,
100
144
  'stt',
101
- `STT API Failure - Called ${method} on stt with lang ${lang}`,
145
+ `STT API Failure - Called ${method}`,
102
146
  error,
103
147
  {
104
148
  requestId,
@@ -107,170 +151,39 @@ const useSTTAPI = (): IuseSTTAPI => {
107
151
  latency,
108
152
  },
109
153
  );
110
- }
111
- };
112
-
113
- const startWithDelay = (lang: LanguageType[]): Promise<string> =>
114
- new Promise(resolve => {
115
- setTimeout(async () => {
116
- const res = await start(lang);
117
- resolve(res);
118
- }, 1000); // Delay of 1 seconds (1000 milliseconds) to allow existing stt service to fully stop
119
- });
120
-
121
- const start = async (lang: LanguageType[]) => {
122
- try {
123
- setIsLangChangeInProgress(true);
124
- const res = await apiCall('startv7', lang);
125
- // null means stt startred successfully
126
- const isSTTAlreadyActive =
127
- res?.error?.message
128
- ?.toLowerCase()
129
- .indexOf('current status is STARTED') !== -1 ||
130
- res?.error?.code === 610 ||
131
- false;
132
-
133
- if (res?.error?.message && res?.error?.code !== 610) {
134
- setIsSTTError(true);
135
- logger.error(
136
- LogSource.NetworkRest,
137
- 'stt',
138
- `start stt for lang ${lang} failed`,
139
- res?.error,
140
- );
141
- } else {
142
- logger.log(
143
- LogSource.NetworkRest,
144
- 'stt',
145
- `start stt for lang ${lang} succesfull`,
146
- res,
147
- );
148
- setIsSTTError(false);
149
- }
150
- if (res === null || isSTTAlreadyActive) {
151
- // once STT is active in the channel , notify others so that they dont' trigger start again
152
- events.send(
153
- EventNames.STT_ACTIVE,
154
- JSON.stringify({active: true}),
155
- PersistanceLevel.Sender,
156
- );
157
- setIsSTTActive(true);
158
- logger.debug(
159
- LogSource.NetworkRest,
160
- 'stt',
161
- `stt lang update from: ${language} to ${lang}`,
162
- );
163
- // inform about the language set for stt
164
- events.send(
165
- EventNames.STT_LANGUAGE,
166
- JSON.stringify({
167
- username: capitalizeFirstLetter(username),
168
- uid: localUid,
169
- prevLang: language,
170
- newLang: lang,
171
- }),
172
- PersistanceLevel.Sender,
173
- );
174
- setLanguage(lang);
175
154
 
176
- // updaing transcript for self
177
- const actionText =
178
- language.indexOf('') !== -1
179
- ? `has set the spoken language to "${getLanguageLabel(lang)}" `
180
- : `changed the spoken language from "${getLanguageLabel(
181
- language,
182
- )}" to "${getLanguageLabel(lang)}" `;
183
- //const msg = `${capitalizeFirstLetter(username)} ${actionText} `;
184
- setMeetingTranscript(prev => {
185
- return [
186
- ...prev,
187
- {
188
- name: 'langUpdate',
189
- time: new Date().getTime(),
190
- uid: `langUpdate-${localUid}`,
191
- text: actionText,
192
- },
193
- ];
194
- });
195
- }
196
- return res;
197
- } catch (errorMsg) {
198
- logger.error(
199
- LogSource.NetworkRest,
200
- 'stt',
201
- 'There was error in start stt',
202
- errorMsg,
203
- );
204
- throw errorMsg;
205
- } finally {
206
- setIsLangChangeInProgress(false);
155
+ return {
156
+ success: false,
157
+ error: {
158
+ message: error?.message || 'Unknown error occurred',
159
+ code: error?.code,
160
+ },
161
+ };
207
162
  }
208
163
  };
209
164
 
210
- const stop = async () => {
211
- try {
212
- const res = await apiCall('stopv7');
213
- // once STT is non-active in the channel , notify others so that they dont' trigger start again
214
- // events.send(
215
- // EventNames.STT_ACTIVE,
216
- // JSON.stringify({active: false}),
217
- // PersistanceLevel.Session,
218
- // );
219
- setIsSTTActive(false);
220
- if (res?.error?.message) {
221
- setIsSTTError(true);
222
- } else {
223
- logger.log(LogSource.NetworkRest, 'stt', 'stop stt succesfull', res);
224
- setIsSTTError(false);
225
- }
226
- return res;
227
- } catch (error) {
228
- logger.error(
229
- LogSource.NetworkRest,
230
- 'stt',
231
- 'There was error in stop stt',
232
- error,
233
- );
234
- throw error;
235
- }
236
- };
237
- const restart = async (lang: LanguageType[]) => {
238
- try {
239
- setIsLangChangeInProgress(true);
240
- await stop();
241
- await startWithDelay(lang);
242
- return Promise.resolve();
243
- } catch (error) {
244
- logger.error(
245
- LogSource.NetworkRest,
246
- 'stt',
247
- 'There was error error in re-starting STT',
248
- error,
249
- );
250
- return Promise.reject(error);
251
- } finally {
252
- setIsLangChangeInProgress(false);
253
- }
165
+ const start = async (
166
+ botUid: number,
167
+ translationConfig: LanguageTranslationConfig,
168
+ ): Promise<STTAPIResponse> => {
169
+ return await apiCall('startv7', botUid, translationConfig);
254
170
  };
255
171
 
256
- // attendee can view option if any host has started STT
257
- const isAuthorizedSTTUser = () =>
258
- $config.ENABLE_STT &&
259
- $config.ENABLE_CAPTION &&
260
- (isHost || (!isHost && isSTTActive));
172
+ const update = async (
173
+ botUid: number,
174
+ translationConfig: LanguageTranslationConfig,
175
+ ): Promise<STTAPIResponse> => {
176
+ return await apiCall('update', botUid, translationConfig);
177
+ };
261
178
 
262
- const isAuthorizedTranscriptUser = () =>
263
- $config.ENABLE_STT &&
264
- $config.ENABLE_CAPTION &&
265
- $config.ENABLE_MEETING_TRANSCRIPT &&
266
- (isHost || (!isHost && isSTTActive));
179
+ const stop = async (botUid: number): Promise<STTAPIResponse> => {
180
+ return await apiCall('stopv7', botUid);
181
+ };
267
182
 
268
183
  return {
269
184
  start,
270
185
  stop,
271
- restart,
272
- isAuthorizedSTTUser,
273
- isAuthorizedTranscriptUser,
186
+ update,
274
187
  };
275
188
  };
276
189
 
@@ -6,10 +6,15 @@ type StreamMessageCallback = (args: [number, Uint8Array]) => void;
6
6
  type FinalListType = {
7
7
  [key: string]: string[];
8
8
  };
9
- type TranscriptItem = {
10
- uid: string;
11
- time: number;
9
+ type TranslationData = {
10
+ lang: string;
12
11
  text: string;
12
+ isFinal: boolean;
13
+ };
14
+ type FinalTranslationListType = {
15
+ [key: string]: {
16
+ [lang: string]: string[];
17
+ };
13
18
  };
14
19
 
15
20
  const useStreamMessageUtils = (): {
@@ -20,27 +25,33 @@ const useStreamMessageUtils = (): {
20
25
  setMeetingTranscript,
21
26
  activeSpeakerRef,
22
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,
23
31
  } = useCaption();
24
32
 
25
33
  let captionStartTime: number = 0;
26
34
  const finalList: FinalListType = {};
27
35
  const finalTranscriptList: FinalListType = {};
36
+ const finalTranslationList: FinalTranslationListType = {};
28
37
 
29
38
  const streamMessageCallback: StreamMessageCallback = args => {
30
39
  /* uid - bot which sends stream message in channel
31
40
  payload - stream message in Uint8Array format
32
41
  */
33
- const [uid, payload] = args;
42
+ const [botUid, payload] = args;
34
43
  let nonFinalText = ''; // holds intermediate results
35
44
  let finalText = ''; // holds final strings
36
45
  let currentFinalText = ''; // holds current caption
37
46
  let isInterjecting = false;
47
+ let translations: TranslationData[] = [];
38
48
 
39
49
  const textstream = protoRoot
40
- .lookupType('Text')
50
+ .lookupType('agora.audio2text.Text')
41
51
  .decode(payload as Uint8Array) as any;
42
52
 
43
- console.log('STT - Parsed Textstream : ', textstream);
53
+ console.log('[STT_PER_USER_BOT] stt v7 textstream', botUid, textstream);
54
+ // console.log('STT - Parsed Textstream : ', textstream);
44
55
 
45
56
  // Identifing Current & Prev Speakers for the Captions
46
57
  /*
@@ -81,6 +92,14 @@ const useStreamMessageUtils = (): {
81
92
  // we have a speaker change so clear the context for prev speaker
82
93
  if (prevSpeakerRef.current !== '') {
83
94
  finalList[prevSpeakerRef.current] = [];
95
+ // Clear translations for previous speaker
96
+ if (finalTranslationList[prevSpeakerRef.current]) {
97
+ Object.keys(finalTranslationList[prevSpeakerRef.current]).forEach(
98
+ lang => {
99
+ finalTranslationList[prevSpeakerRef.current][lang] = [];
100
+ },
101
+ );
102
+ }
84
103
  isInterjecting = true;
85
104
  }
86
105
  prevSpeakerRef.current = activeSpeakerRef.current;
@@ -96,6 +115,51 @@ const useStreamMessageUtils = (): {
96
115
  finalTranscriptList[textstream.uid] = [];
97
116
  }
98
117
 
118
+ /* Process translations if available */
119
+ if (textstream.trans && textstream.trans.length > 0) {
120
+ for (const trans of textstream.trans) {
121
+ const lang = trans.lang;
122
+ const texts = trans.texts || [];
123
+ const isFinal = trans.isFinal || false;
124
+
125
+ if (!finalTranslationList[textstream.uid]) {
126
+ finalTranslationList[textstream.uid] = {};
127
+ }
128
+ if (!finalTranslationList[textstream.uid][lang]) {
129
+ finalTranslationList[textstream.uid][lang] = [];
130
+ }
131
+
132
+ const currentTranslationText = texts.join(' ');
133
+ if (currentTranslationText) {
134
+ if (isFinal) {
135
+ finalTranslationList[textstream.uid][lang].push(
136
+ currentTranslationText,
137
+ );
138
+ }
139
+
140
+ // Build complete translation text (final + current non-final)
141
+ const existingTranslationBuffer = isInterjecting
142
+ ? ''
143
+ : finalTranslationList[textstream.uid][lang]?.join(' ');
144
+ const latestTranslationString = isFinal ? '' : currentTranslationText;
145
+ const completeTranslationText =
146
+ existingTranslationBuffer.length > 0
147
+ ? latestTranslationString
148
+ ? existingTranslationBuffer + ' ' + latestTranslationString
149
+ : existingTranslationBuffer
150
+ : latestTranslationString;
151
+
152
+ if (completeTranslationText || isFinal) {
153
+ translations.push({
154
+ lang,
155
+ text: completeTranslationText,
156
+ isFinal,
157
+ });
158
+ }
159
+ }
160
+ }
161
+ }
162
+
99
163
  const words = textstream.words; //[Word,Word]
100
164
 
101
165
  /* categorize words into final & nonFinal objects per uid
@@ -105,7 +169,7 @@ const useStreamMessageUtils = (): {
105
169
  "isFinal": true,
106
170
  "confidence": 0.8549408316612244
107
171
  }
108
- */
172
+ */
109
173
  for (const word of words) {
110
174
  if (word.isFinal) {
111
175
  finalText = finalText + word.text;
@@ -130,32 +194,57 @@ const useStreamMessageUtils = (): {
130
194
  }
131
195
 
132
196
  /* Updating Meeting Transcript */
133
- if (currentFinalText.length) {
197
+ // Update transcript when: (1) new text finalized OR (2) final translations arrived
198
+ const hasFinalTranslations = textstream.trans?.some(
199
+ (t: any) => t.isFinal === true,
200
+ );
201
+
202
+ if (currentFinalText.length || hasFinalTranslations) {
203
+ // Prepare final translations for transcript
204
+ const finalTranslationsForTranscript: TranslationData[] = [];
205
+ if (finalTranslationList[textstream.uid]) {
206
+ Object.keys(finalTranslationList[textstream.uid]).forEach(lang => {
207
+ const translationText =
208
+ finalTranslationList[textstream.uid][lang]?.join(' ') || '';
209
+
210
+ if (translationText) {
211
+ finalTranslationsForTranscript.push({
212
+ lang: lang,
213
+ text: translationText,
214
+ isFinal: true,
215
+ });
216
+ }
217
+ });
218
+ }
219
+
134
220
  setMeetingTranscript(prevTranscript => {
135
221
  const lastTranscriptIndex = prevTranscript.length - 1;
136
222
  const lastTranscript =
137
223
  lastTranscriptIndex >= 0 ? prevTranscript[lastTranscriptIndex] : null;
138
224
 
139
225
  /*
140
- checking if the last item transcript matches with current uid
141
- If yes then updating the last transcript msg with current text
226
+ checking if the last item transcript matches with current uid
227
+ If yes then updating the last transcript msg with current text and translations
142
228
  If no then adding a new entry in the transcript
143
229
  */
144
230
  if (lastTranscript && lastTranscript.uid === textstream.uid) {
145
231
  const updatedTranscript = {
146
232
  ...lastTranscript,
147
233
  //text: lastTranscript.text + ' ' + currentFinalText, // missing few updates with reading prev values
148
- text: finalTranscriptList[textstream.uid].join(' '),
234
+ text: currentFinalText.length
235
+ ? finalTranscriptList[textstream.uid].join(' ')
236
+ : lastTranscript.text, // Keep existing text if no new text
237
+ translations: finalTranslationsForTranscript,
238
+ // preserve the original translation language from when this transcript was created
239
+ selectedTranslationLanguage:
240
+ lastTranscript.selectedTranslationLanguage,
149
241
  };
150
242
 
151
243
  return [
152
244
  ...prevTranscript.slice(0, lastTranscriptIndex),
153
245
  updatedTranscript,
154
246
  ];
155
- } else {
156
- const isLangUpdate =
157
- lastTranscript?.uid.toString().indexOf('langUpdate') > -1;
158
-
247
+ } else if (currentFinalText.length) {
159
248
  finalTranscriptList[textstream.uid] = [currentFinalText];
160
249
 
161
250
  return [
@@ -164,36 +253,66 @@ const useStreamMessageUtils = (): {
164
253
  uid: textstream.uid,
165
254
  time: new Date().getTime(),
166
255
  text: currentFinalText,
256
+ translations: finalTranslationsForTranscript,
257
+ // Store the current translation language with this transcript item
258
+ // This preserves which translation was active when this text was spoken
259
+ selectedTranslationLanguage:
260
+ selectedTranslationLanguageRef.current,
167
261
  },
168
262
  ];
263
+ } else {
264
+ // No new text and uid doesn't match - don't modify transcript
265
+ // console.log(
266
+ // '[TRANSCRIPT_DEBUG] Skipping transcript update - no new text and uid mismatch',
267
+ // );
268
+ return prevTranscript;
169
269
  }
170
270
  });
171
271
  }
172
272
 
173
- /*
174
- Previous final words of the uid are prepended and
273
+ /*
274
+ Previous final words of the uid are prepended and
175
275
  then current non final words so that context of speech is not lost
176
276
  */
177
277
  const existingStringBuffer = isInterjecting
178
278
  ? ''
179
279
  : finalList[textstream.uid]?.join(' ');
180
280
  const latestString = nonFinalText;
181
- const captionText =
182
- existingStringBuffer.length > 0
183
- ? existingStringBuffer + ' ' + latestString
184
- : latestString;
185
-
186
- // updating the captions
187
- captionText &&
188
- setCaptionObj(prevState => {
189
- return {
190
- ...prevState,
191
- [textstream.uid]: {
192
- text: captionText,
193
- lastUpdated: new Date().getTime(),
194
- },
195
- };
196
- });
281
+ const captionText = isInterjecting
282
+ ? latestString
283
+ : existingStringBuffer.length > 0
284
+ ? existingStringBuffer + ' ' + latestString
285
+ : latestString;
286
+
287
+ // updating the captions with translations
288
+ setCaptionObj(prevState => {
289
+ const existingTranslations =
290
+ prevState[textstream.uid]?.translations || [];
291
+
292
+ // Update existing translations or add new ones
293
+ const updatedTranslations = [...existingTranslations];
294
+
295
+ for (const newTrans of translations) {
296
+ const existingIndex = updatedTranslations.findIndex(
297
+ t => t.lang === newTrans.lang,
298
+ );
299
+
300
+ if (existingIndex >= 0) {
301
+ updatedTranslations[existingIndex] = newTrans;
302
+ } else {
303
+ updatedTranslations.push(newTrans);
304
+ }
305
+ }
306
+
307
+ return {
308
+ ...prevState,
309
+ [textstream.uid]: {
310
+ text: captionText || prevState[textstream.uid]?.text || '',
311
+ translations: updatedTranslations,
312
+ lastUpdated: new Date().getTime(),
313
+ },
314
+ };
315
+ });
197
316
 
198
317
  console.group('STT-logs');
199
318
  console.log('Recived uid =>', textstream.uid);