agora-appbuilder-core 4.1.8-beta.1 → 4.1.8-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -1
- package/template/agora-rn-uikit/src/Rtc/Join.tsx +18 -9
- package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
- package/template/defaultConfig.js +4 -3
- package/template/global.d.ts +1 -0
- package/template/package.json +1 -1
- package/template/src/atoms/TextInput.tsx +3 -0
- package/template/src/components/Controls.tsx +17 -15
- package/template/src/components/common/GenericPopup.tsx +0 -1
- package/template/src/components/common/data-table.tsx +42 -15
- package/template/src/components/controls/useControlPermissionMatrix.tsx +7 -4
- package/template/src/components/recordings/RecordingItemRow.tsx +289 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +99 -25
- package/template/src/components/recordings/TextTrackItemRow.tsx +120 -0
- package/template/src/components/room-info/useRoomInfo.tsx +1 -0
- package/template/src/components/{stt-transcript/STTTranscriptTable.tsx → text-tracks/TextTracksTable.tsx} +61 -50
- package/template/src/components/{stt-transcript/ViewSTTTranscriptModal.tsx → text-tracks/ViewTextTracksModal.tsx} +7 -7
- package/template/src/components/text-tracks/useFetchSTTTranscript.tsx +262 -0
- package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -7
- package/template/src/pages/VideoCall.tsx +1 -1
- package/template/src/subComponents/ChatInput.tsx +72 -1
- package/template/src/subComponents/recording/useRecording.tsx +19 -4
- package/template/src/utils/useCreateRoom.ts +1 -1
- package/template/src/utils/useJoinRoom.ts +5 -1
- package/template/src/components/stt-transcript/useFetchSTTTranscript.tsx +0 -193
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import {useState, useCallback, useEffect, useContext} from 'react';
|
|
2
|
+
import StorageContext from '../StorageContext';
|
|
3
|
+
import {useRoomInfo} from 'customization-api';
|
|
4
|
+
import getUniqueID from '../../utils/getUniqueID';
|
|
5
|
+
import {logger, LogSource} from '../../logger/AppBuilderLogger';
|
|
6
|
+
|
|
7
|
+
export interface FetchSTTTranscriptResponse {
|
|
8
|
+
pagination: {limit: number; total: number; page: number};
|
|
9
|
+
stts: {
|
|
10
|
+
id: string;
|
|
11
|
+
download_url: string[];
|
|
12
|
+
title: string;
|
|
13
|
+
product_name: string;
|
|
14
|
+
status: 'COMPLETED' | 'STARTED' | 'INPROGRESS' | 'STOPPING';
|
|
15
|
+
created_at: string;
|
|
16
|
+
ended_at: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
|
|
21
|
+
|
|
22
|
+
export function useFetchSTTTranscript() {
|
|
23
|
+
const {
|
|
24
|
+
data: {roomId},
|
|
25
|
+
} = useRoomInfo();
|
|
26
|
+
const {store} = useContext(StorageContext);
|
|
27
|
+
|
|
28
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
29
|
+
|
|
30
|
+
const [sttState, setSttState] = useState<{
|
|
31
|
+
status: APIStatus;
|
|
32
|
+
data: {
|
|
33
|
+
stts: FetchSTTTranscriptResponse['stts'];
|
|
34
|
+
pagination: FetchSTTTranscriptResponse['pagination'];
|
|
35
|
+
};
|
|
36
|
+
error: Error | null;
|
|
37
|
+
}>({
|
|
38
|
+
status: 'idle',
|
|
39
|
+
data: {stts: [], pagination: {total: 0, limit: 10, page: 1}},
|
|
40
|
+
error: null,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
//–– by‐recording state ––
|
|
44
|
+
const [sttRecState, setSttRecState] = useState<{
|
|
45
|
+
status: APIStatus;
|
|
46
|
+
data: {stts: FetchSTTTranscriptResponse['stts']};
|
|
47
|
+
error: Error | null;
|
|
48
|
+
}>({
|
|
49
|
+
status: 'idle',
|
|
50
|
+
data: {
|
|
51
|
+
stts: [],
|
|
52
|
+
},
|
|
53
|
+
error: null,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const getSTTs = useCallback(
|
|
57
|
+
(page: number) => {
|
|
58
|
+
setSttState(s => ({...s, status: 'pending', error: null}));
|
|
59
|
+
const reqId = getUniqueID();
|
|
60
|
+
const start = Date.now();
|
|
61
|
+
|
|
62
|
+
fetch(`${$config.BACKEND_ENDPOINT}/v1/stt-transcript`, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
'Content-Type': 'application/json',
|
|
66
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
67
|
+
'X-Request-Id': reqId,
|
|
68
|
+
'X-Session-Id': logger.getSessionId(),
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
passphrase: roomId.host,
|
|
72
|
+
limit: 10,
|
|
73
|
+
page,
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
.then(async res => {
|
|
77
|
+
const json = await res.json();
|
|
78
|
+
const end = Date.now();
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
logger.error(
|
|
81
|
+
LogSource.NetworkRest,
|
|
82
|
+
'stt-transcript',
|
|
83
|
+
'Fetch STT transcripts failed',
|
|
84
|
+
{
|
|
85
|
+
json,
|
|
86
|
+
start,
|
|
87
|
+
end,
|
|
88
|
+
latency: end - start,
|
|
89
|
+
requestId: reqId,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
throw new Error(json?.error?.message || res.statusText);
|
|
93
|
+
}
|
|
94
|
+
logger.debug(
|
|
95
|
+
LogSource.NetworkRest,
|
|
96
|
+
'stt-transcript',
|
|
97
|
+
'Fetch STT transcripts succeeded',
|
|
98
|
+
{
|
|
99
|
+
json,
|
|
100
|
+
start,
|
|
101
|
+
end,
|
|
102
|
+
latency: end - start,
|
|
103
|
+
requestId: reqId,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
return json as FetchSTTTranscriptResponse;
|
|
107
|
+
})
|
|
108
|
+
.then(({stts = [], pagination = {total: 0, limit: 10, page}}) => {
|
|
109
|
+
setSttState({
|
|
110
|
+
status: 'resolved',
|
|
111
|
+
data: {stts, pagination},
|
|
112
|
+
error: null,
|
|
113
|
+
});
|
|
114
|
+
})
|
|
115
|
+
.catch(err => {
|
|
116
|
+
setSttState(s => ({...s, status: 'rejected', error: err}));
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
[roomId.host, store.token],
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Delete stts
|
|
123
|
+
const deleteTranscript = useCallback(
|
|
124
|
+
async (id: string) => {
|
|
125
|
+
const reqId = getUniqueID();
|
|
126
|
+
const start = Date.now();
|
|
127
|
+
|
|
128
|
+
const res = await fetch(
|
|
129
|
+
`${
|
|
130
|
+
$config.BACKEND_ENDPOINT
|
|
131
|
+
}/v1/stt-transcript/${id}?passphrase=${encodeURIComponent(
|
|
132
|
+
roomId.host,
|
|
133
|
+
)}`,
|
|
134
|
+
{
|
|
135
|
+
method: 'DELETE',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
139
|
+
'X-Request-Id': reqId,
|
|
140
|
+
'X-Session-Id': logger.getSessionId(),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
const end = Date.now();
|
|
145
|
+
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
logger.error(
|
|
148
|
+
LogSource.NetworkRest,
|
|
149
|
+
'stt-transcript',
|
|
150
|
+
'Delete transcript failed',
|
|
151
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
152
|
+
);
|
|
153
|
+
throw new Error(`Delete failed (${res.status})`);
|
|
154
|
+
}
|
|
155
|
+
logger.debug(
|
|
156
|
+
LogSource.NetworkRest,
|
|
157
|
+
'stt-transcript',
|
|
158
|
+
'Delete transcript succeeded',
|
|
159
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// optimistic remove from paginated list
|
|
163
|
+
setSttState(prev => {
|
|
164
|
+
// remove the deleted item
|
|
165
|
+
const newStts = prev.data.stts.filter(item => item.id !== id);
|
|
166
|
+
// decrement total count
|
|
167
|
+
const newTotal = Math.max(prev.data.pagination.total - 1, 0);
|
|
168
|
+
let newPage = prev.data.pagination.page;
|
|
169
|
+
if (prev.data.stts.length === 1 && newPage > 1) {
|
|
170
|
+
newPage--;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
...prev,
|
|
174
|
+
data: {
|
|
175
|
+
stts: newStts,
|
|
176
|
+
|
|
177
|
+
pagination: {
|
|
178
|
+
...prev.data.pagination,
|
|
179
|
+
total: newTotal,
|
|
180
|
+
page: newPage,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
[roomId.host, store.token],
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
//–– fetch for a given recording ––
|
|
190
|
+
const getSTTsForRecording = useCallback(
|
|
191
|
+
(recordingId: string) => {
|
|
192
|
+
setSttRecState(r => ({...r, status: 'pending', error: null}));
|
|
193
|
+
const reqId = getUniqueID();
|
|
194
|
+
const start = Date.now();
|
|
195
|
+
|
|
196
|
+
fetch(`${$config.BACKEND_ENDPOINT}/v1/recording/stt-transcript`, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: {
|
|
199
|
+
'Content-Type': 'application/json',
|
|
200
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
201
|
+
'X-Request-Id': reqId,
|
|
202
|
+
'X-Session-Id': logger.getSessionId(),
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify({
|
|
205
|
+
project_id: $config.PROJECT_ID,
|
|
206
|
+
recording_id: recordingId,
|
|
207
|
+
}),
|
|
208
|
+
})
|
|
209
|
+
.then(async res => {
|
|
210
|
+
const json = await res.json();
|
|
211
|
+
const end = Date.now();
|
|
212
|
+
console.log('supriua json', json);
|
|
213
|
+
if (!res.ok) {
|
|
214
|
+
logger.error(
|
|
215
|
+
LogSource.NetworkRest,
|
|
216
|
+
'stt-transcript',
|
|
217
|
+
'Fetch stt-by-recording failed',
|
|
218
|
+
{json, start, end, latency: end - start, requestId: reqId},
|
|
219
|
+
);
|
|
220
|
+
throw new Error(json?.error?.message || res.statusText);
|
|
221
|
+
}
|
|
222
|
+
logger.debug(
|
|
223
|
+
LogSource.NetworkRest,
|
|
224
|
+
'stt-transcript',
|
|
225
|
+
'Fetch stt-by-recording succeeded',
|
|
226
|
+
{json, start, end, latency: end - start, requestId: reqId},
|
|
227
|
+
);
|
|
228
|
+
if (json?.error) {
|
|
229
|
+
logger.debug(
|
|
230
|
+
LogSource.NetworkRest,
|
|
231
|
+
'stt-transcript',
|
|
232
|
+
`No STT records found (code ${json.error.code}): ${json.error.message}`,
|
|
233
|
+
{start, end, latency: end - start, reqId},
|
|
234
|
+
);
|
|
235
|
+
return [];
|
|
236
|
+
} else {
|
|
237
|
+
return json as FetchSTTTranscriptResponse['stts'];
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
.then(stts =>
|
|
241
|
+
setSttRecState({status: 'resolved', data: {stts}, error: null}),
|
|
242
|
+
)
|
|
243
|
+
.catch(err =>
|
|
244
|
+
setSttRecState(r => ({...r, status: 'rejected', error: err})),
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
[store.token],
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
// stt list
|
|
252
|
+
sttState,
|
|
253
|
+
getSTTs,
|
|
254
|
+
currentPage,
|
|
255
|
+
setCurrentPage,
|
|
256
|
+
// STT per recording
|
|
257
|
+
sttRecState,
|
|
258
|
+
getSTTsForRecording,
|
|
259
|
+
// delete
|
|
260
|
+
deleteTranscript,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -109,8 +109,8 @@ export const toolbarItemNoiseCancellationText =
|
|
|
109
109
|
export const toolbarItemWhiteboardText = 'toolbarItemWhiteboardText';
|
|
110
110
|
export const toolbarItemCaptionText = 'toolbarItemCaptionText';
|
|
111
111
|
export const toolbarItemTranscriptText = 'toolbarItemTranscriptText';
|
|
112
|
-
export const
|
|
113
|
-
'
|
|
112
|
+
export const toolbarItemManageTextTracksText =
|
|
113
|
+
'toolbarItemManageTextTracksText';
|
|
114
114
|
export const toolbarItemVirtualBackgroundText =
|
|
115
115
|
'toolbarItemVirtualBackgroundText';
|
|
116
116
|
export const toolbarItemViewRecordingText = 'toolbarItemViewRecordingText';
|
|
@@ -151,7 +151,7 @@ export const nativeStopScreensharePopupPrimaryBtnText =
|
|
|
151
151
|
'nativeStopScreensharePopupPrimaryBtnText';
|
|
152
152
|
|
|
153
153
|
export const recordingModalTitleIntn = 'recordingModalTitleIntn';
|
|
154
|
-
export const
|
|
154
|
+
export const textTrackModalTitleIntn = 'textTrackModalTitleIntn';
|
|
155
155
|
export const stopRecordingPopupHeading = 'stopRecordingPopupHeading';
|
|
156
156
|
export const stopRecordingPopupSubHeading = 'stopRecordingPopupSubHeading';
|
|
157
157
|
export const stopRecordingPopupPrimaryBtnText =
|
|
@@ -573,7 +573,7 @@ export interface I18nVideoCallScreenLabelsInterface {
|
|
|
573
573
|
[toolbarItemWhiteboardText]?: I18nConditionalType;
|
|
574
574
|
[toolbarItemCaptionText]?: I18nConditionalType;
|
|
575
575
|
[toolbarItemTranscriptText]?: I18nConditionalType;
|
|
576
|
-
[
|
|
576
|
+
[toolbarItemManageTextTracksText]?: I18nConditionalType;
|
|
577
577
|
[toolbarItemVirtualBackgroundText]?: I18nBaseType;
|
|
578
578
|
[toolbarItemViewRecordingText]?: I18nConditionalType;
|
|
579
579
|
|
|
@@ -609,7 +609,7 @@ export interface I18nVideoCallScreenLabelsInterface {
|
|
|
609
609
|
[nativeStopScreensharePopupPrimaryBtnText]?: I18nBaseType;
|
|
610
610
|
|
|
611
611
|
[recordingModalTitleIntn]?: I18nBaseType;
|
|
612
|
-
[
|
|
612
|
+
[textTrackModalTitleIntn]?: I18nBaseType;
|
|
613
613
|
[stopRecordingPopupHeading]?: I18nBaseType;
|
|
614
614
|
[stopRecordingPopupSubHeading]?: I18nBaseType;
|
|
615
615
|
[stopRecordingPopupPrimaryBtnText]?: I18nBaseType;
|
|
@@ -942,7 +942,7 @@ export const VideoCallScreenLabels: I18nVideoCallScreenLabelsInterface = {
|
|
|
942
942
|
[toolbarItemTranscriptText]: active =>
|
|
943
943
|
active ? 'Hide Transcript' : 'Show Transcript',
|
|
944
944
|
[toolbarItemViewRecordingText]: 'View Recordings',
|
|
945
|
-
[
|
|
945
|
+
[toolbarItemManageTextTracksText]: 'View Text-tracks',
|
|
946
946
|
|
|
947
947
|
[toolbarItemRaiseHandText]: active => (active ? 'Lower Hand' : 'Raise Hand'),
|
|
948
948
|
[toolbarItemSwitchCameraText]: 'Switch Camera',
|
|
@@ -1025,7 +1025,7 @@ export const VideoCallScreenLabels: I18nVideoCallScreenLabelsInterface = {
|
|
|
1025
1025
|
`Once removed, ${name} will still be able to screen share later.`,
|
|
1026
1026
|
[removeScreenshareFromRoomPopupPrimaryBtnText]: 'REMOVE',
|
|
1027
1027
|
|
|
1028
|
-
[
|
|
1028
|
+
[textTrackModalTitleIntn]: 'Text Tracks',
|
|
1029
1029
|
[sttChangeLanguagePopupHeading]: isFirstTimeOpened =>
|
|
1030
1030
|
isFirstTimeOpened ? 'Set Spoken Language' : 'Change Spoken Language',
|
|
1031
1031
|
[sttChangeLanguagePopupSubHeading]:
|
|
@@ -369,7 +369,7 @@ const VideoCall: React.FC = () => {
|
|
|
369
369
|
encryption: $config.ENCRYPTION_ENABLED
|
|
370
370
|
? {
|
|
371
371
|
key: data.encryptionSecret,
|
|
372
|
-
mode:
|
|
372
|
+
mode: data.encryptionMode,
|
|
373
373
|
screenKey: data.encryptionSecret,
|
|
374
374
|
salt: data.encryptionSecretSalt,
|
|
375
375
|
}
|
|
@@ -97,6 +97,10 @@ export const ChatTextInput = (props: ChatTextInputProps) => {
|
|
|
97
97
|
replyToMsgId,
|
|
98
98
|
setReplyToMsgId,
|
|
99
99
|
} = useChatUIControls();
|
|
100
|
+
|
|
101
|
+
// Track IME composition state
|
|
102
|
+
const [isComposing, setIsComposing] = React.useState(false);
|
|
103
|
+
|
|
100
104
|
const {defaultContent} = useContent();
|
|
101
105
|
const {sendChatSDKMessage, uploadAttachment} = useChatConfigure();
|
|
102
106
|
const {addMessageToPrivateStore, addMessageToStore} = useChatMessages();
|
|
@@ -115,6 +119,41 @@ export const ChatTextInput = (props: ChatTextInputProps) => {
|
|
|
115
119
|
});
|
|
116
120
|
}, []);
|
|
117
121
|
|
|
122
|
+
// Set up direct DOM event listeners for IME composition
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!isWeb()) return;
|
|
125
|
+
|
|
126
|
+
const inputElement = chatInputRef?.current;
|
|
127
|
+
if (!inputElement) return;
|
|
128
|
+
|
|
129
|
+
// Get the actual DOM element (React Native Web creates a textarea/input)
|
|
130
|
+
const domElement = inputElement._nativeTag
|
|
131
|
+
? document.querySelector(`[data-tag="${inputElement._nativeTag}"]`)
|
|
132
|
+
: inputElement;
|
|
133
|
+
|
|
134
|
+
if (!domElement) return;
|
|
135
|
+
|
|
136
|
+
const handleCompositionStart = () => {
|
|
137
|
+
setIsComposing(true);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleCompositionEnd = () => {
|
|
141
|
+
setIsComposing(false);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Add event listeners directly to DOM element
|
|
145
|
+
domElement.addEventListener('compositionstart', handleCompositionStart);
|
|
146
|
+
domElement.addEventListener('compositionend', handleCompositionEnd);
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
domElement.removeEventListener(
|
|
150
|
+
'compositionstart',
|
|
151
|
+
handleCompositionStart,
|
|
152
|
+
);
|
|
153
|
+
domElement.removeEventListener('compositionend', handleCompositionEnd);
|
|
154
|
+
};
|
|
155
|
+
}, [chatInputRef?.current]);
|
|
156
|
+
|
|
118
157
|
const {data} = useRoomInfo();
|
|
119
158
|
const [name] = useUserName();
|
|
120
159
|
const toastHeadingSize = useString(chatSendErrorTextSizeToastHeading)();
|
|
@@ -166,9 +205,37 @@ export const ChatTextInput = (props: ChatTextInputProps) => {
|
|
|
166
205
|
});
|
|
167
206
|
};
|
|
168
207
|
|
|
208
|
+
// IME composition handlers
|
|
209
|
+
const handleCompositionStart = () => {
|
|
210
|
+
setIsComposing(true);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleCompositionEnd = () => {
|
|
214
|
+
setIsComposing(false);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const handleInput = event => {
|
|
218
|
+
// Reset composition state if input event occurs without active composition
|
|
219
|
+
if (isWeb() && !event.nativeEvent.isComposing && isComposing) {
|
|
220
|
+
setIsComposing(false);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
169
224
|
// with multiline textinput enter prints /n
|
|
170
225
|
const handleKeyPress = ({nativeEvent}) => {
|
|
171
|
-
|
|
226
|
+
const currentlyComposing = nativeEvent.isComposing || isComposing;
|
|
227
|
+
|
|
228
|
+
// Check if this is an Enter key during composition
|
|
229
|
+
if (nativeEvent.key === 'Enter' && currentlyComposing) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Only submit on Enter if not composing with IME and no Shift key
|
|
234
|
+
if (
|
|
235
|
+
nativeEvent.key === 'Enter' &&
|
|
236
|
+
!nativeEvent.shiftKey &&
|
|
237
|
+
!currentlyComposing
|
|
238
|
+
) {
|
|
172
239
|
nativeEvent.preventDefault();
|
|
173
240
|
onSubmitEditing();
|
|
174
241
|
setShowEmojiPicker(false); // This will close emoji picker on enter
|
|
@@ -225,6 +292,10 @@ export const ChatTextInput = (props: ChatTextInputProps) => {
|
|
|
225
292
|
autoCorrect={false}
|
|
226
293
|
onKeyPress={handleKeyPress}
|
|
227
294
|
onChange={_handleHeightChange}
|
|
295
|
+
// IME composition event handlers for React Native Web
|
|
296
|
+
onCompositionStart={isWeb() ? handleCompositionStart : undefined}
|
|
297
|
+
onCompositionEnd={isWeb() ? handleCompositionEnd : undefined}
|
|
298
|
+
onInput={isWeb() ? handleInput : undefined}
|
|
228
299
|
/>
|
|
229
300
|
);
|
|
230
301
|
|
|
@@ -67,16 +67,31 @@ const getFrontendUrl = (url: string) => {
|
|
|
67
67
|
return url;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
|
|
71
|
+
|
|
72
|
+
export interface FetchRecordingData {
|
|
73
|
+
pagination: {
|
|
74
|
+
limit: number;
|
|
75
|
+
total: number;
|
|
76
|
+
page: number;
|
|
77
|
+
};
|
|
78
|
+
recordings: {
|
|
79
|
+
id: string;
|
|
80
|
+
download_url: string[];
|
|
81
|
+
title: string;
|
|
82
|
+
product_name: string;
|
|
83
|
+
status: 'COMPLETED' | 'STARTED' | 'INPROGRESS' | 'STOPPING';
|
|
84
|
+
created_at: string;
|
|
85
|
+
ended_at: string;
|
|
86
|
+
}[];
|
|
73
87
|
}
|
|
88
|
+
|
|
74
89
|
export interface RecordingContextInterface {
|
|
75
90
|
startRecording: () => void;
|
|
76
91
|
stopRecording: () => void;
|
|
77
92
|
isRecordingActive: boolean;
|
|
78
93
|
inProgress: boolean;
|
|
79
|
-
fetchRecordings?: (page: number) => Promise<
|
|
94
|
+
fetchRecordings?: (page: number) => Promise<FetchRecordingData>;
|
|
80
95
|
deleteRecording?: (id: number) => Promise<boolean>;
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -58,7 +58,6 @@ export default function useJoinRoom() {
|
|
|
58
58
|
send_event: false,
|
|
59
59
|
});
|
|
60
60
|
} else {
|
|
61
|
-
console.log('debugging store.token', store.token);
|
|
62
61
|
logger.log(
|
|
63
62
|
LogSource.NetworkRest,
|
|
64
63
|
'joinChannel',
|
|
@@ -157,6 +156,11 @@ export default function useJoinRoom() {
|
|
|
157
156
|
isWaitingRoomEnabled ? data.secretSalt : data.secret_salt,
|
|
158
157
|
) as Uint8Array;
|
|
159
158
|
}
|
|
159
|
+
|
|
160
|
+
if (data?.encryption_mode) {
|
|
161
|
+
roomInfo.encryptionMode = data.encryption_mode;
|
|
162
|
+
}
|
|
163
|
+
|
|
160
164
|
if (data?.screen_share_user?.uid || data?.screenShare?.uid) {
|
|
161
165
|
roomInfo.screenShareUid = isWaitingRoomEnabled
|
|
162
166
|
? data.screenShare.uid
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import {useState, useCallback, useEffect, useContext} from 'react';
|
|
2
|
-
import StorageContext from '../StorageContext';
|
|
3
|
-
import {useRoomInfo} from 'customization-api';
|
|
4
|
-
import getUniqueID from '../../utils/getUniqueID';
|
|
5
|
-
import {logger, LogSource} from '../../logger/AppBuilderLogger';
|
|
6
|
-
|
|
7
|
-
export interface FetchSTTTranscriptResponse {
|
|
8
|
-
pagination: {
|
|
9
|
-
limit: number;
|
|
10
|
-
total: number;
|
|
11
|
-
page: number;
|
|
12
|
-
};
|
|
13
|
-
stts: {
|
|
14
|
-
id: string;
|
|
15
|
-
download_url: string[];
|
|
16
|
-
title: string;
|
|
17
|
-
product_name: string;
|
|
18
|
-
status: 'COMPLETED' | 'STARTED' | 'INPROGRESS' | 'STOPPING';
|
|
19
|
-
created_at: string;
|
|
20
|
-
ended_at: string;
|
|
21
|
-
}[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
|
|
25
|
-
|
|
26
|
-
export function useFetchSTTTranscript(defaultLimit = 10) {
|
|
27
|
-
const {
|
|
28
|
-
data: {roomId},
|
|
29
|
-
} = useRoomInfo();
|
|
30
|
-
const {store} = useContext(StorageContext);
|
|
31
|
-
const [currentPage, setCurrentPage] = useState(1);
|
|
32
|
-
|
|
33
|
-
const [state, setState] = useState<{
|
|
34
|
-
status: APIStatus;
|
|
35
|
-
data: {
|
|
36
|
-
stts: FetchSTTTranscriptResponse['stts'];
|
|
37
|
-
pagination: FetchSTTTranscriptResponse['pagination'];
|
|
38
|
-
};
|
|
39
|
-
error: any;
|
|
40
|
-
}>({
|
|
41
|
-
status: 'idle',
|
|
42
|
-
data: {stts: [], pagination: {total: 0, limit: defaultLimit, page: 1}},
|
|
43
|
-
error: null,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const fetchStts = useCallback(
|
|
47
|
-
async (page: number) => {
|
|
48
|
-
const requestId = getUniqueID();
|
|
49
|
-
const start = Date.now();
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
if (!roomId?.host) {
|
|
53
|
-
return Promise.reject('room id is empty');
|
|
54
|
-
}
|
|
55
|
-
const res = await fetch(
|
|
56
|
-
`${$config.BACKEND_ENDPOINT}/v1/stt-transcript`,
|
|
57
|
-
{
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: {
|
|
60
|
-
'Content-Type': 'application/json',
|
|
61
|
-
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
62
|
-
'X-Request-Id': requestId,
|
|
63
|
-
'X-Session-Id': logger.getSessionId(),
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify({
|
|
66
|
-
passphrase: roomId.host,
|
|
67
|
-
limit: defaultLimit,
|
|
68
|
-
page,
|
|
69
|
-
}),
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
const json = await res.json();
|
|
73
|
-
const end = Date.now();
|
|
74
|
-
|
|
75
|
-
if (!res.ok) {
|
|
76
|
-
logger.error(
|
|
77
|
-
LogSource.NetworkRest,
|
|
78
|
-
'stt-transcript',
|
|
79
|
-
'Fetching STT transcripts failed',
|
|
80
|
-
{
|
|
81
|
-
json,
|
|
82
|
-
start,
|
|
83
|
-
end,
|
|
84
|
-
latency: end - start,
|
|
85
|
-
requestId,
|
|
86
|
-
},
|
|
87
|
-
);
|
|
88
|
-
throw new Error(json?.error?.message || 'Unknown fetch error');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
logger.debug(
|
|
92
|
-
LogSource.NetworkRest,
|
|
93
|
-
'stt-transcript',
|
|
94
|
-
'Fetched STT transcripts',
|
|
95
|
-
{
|
|
96
|
-
json,
|
|
97
|
-
start,
|
|
98
|
-
end,
|
|
99
|
-
latency: end - start,
|
|
100
|
-
requestId,
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
return json;
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return Promise.reject(err);
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
[roomId.host, store.token, defaultLimit],
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
const getSTTs = useCallback(
|
|
112
|
-
(page: number) => {
|
|
113
|
-
setState(s => ({...s, status: 'pending'}));
|
|
114
|
-
fetchStts(page).then(
|
|
115
|
-
data =>
|
|
116
|
-
setState({
|
|
117
|
-
status: 'resolved',
|
|
118
|
-
data: {
|
|
119
|
-
stts: data.stts || [],
|
|
120
|
-
pagination: data.pagination || {
|
|
121
|
-
total: 0,
|
|
122
|
-
limit: defaultLimit,
|
|
123
|
-
page: 1,
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
error: null,
|
|
127
|
-
}),
|
|
128
|
-
err => setState(s => ({...s, status: 'rejected', error: err})),
|
|
129
|
-
);
|
|
130
|
-
},
|
|
131
|
-
[fetchStts, defaultLimit],
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const deleteTranscript = useCallback(
|
|
135
|
-
async (id: string) => {
|
|
136
|
-
const res = await fetch(
|
|
137
|
-
`${
|
|
138
|
-
$config.BACKEND_ENDPOINT
|
|
139
|
-
}/v1/stt-transcript/${id}?passphrase=${encodeURIComponent(
|
|
140
|
-
roomId.host,
|
|
141
|
-
)}`,
|
|
142
|
-
{
|
|
143
|
-
method: 'DELETE',
|
|
144
|
-
headers: {'Content-Type': 'application/json'},
|
|
145
|
-
},
|
|
146
|
-
);
|
|
147
|
-
if (!res.ok) {
|
|
148
|
-
const body = await res.json();
|
|
149
|
-
throw new Error(
|
|
150
|
-
body?.error?.message ?? `Delete failed (${res.status})`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
// optimistic update local state:
|
|
154
|
-
setState(prev => {
|
|
155
|
-
// remove the deleted item
|
|
156
|
-
const newStts = prev.data.stts.filter(item => item.id !== id);
|
|
157
|
-
// decrement total count
|
|
158
|
-
const newTotal = Math.max(prev.data.pagination.total - 1, 0);
|
|
159
|
-
// if we just removed the *last* item on this page, go back a page
|
|
160
|
-
let newPage = prev.data.pagination.page;
|
|
161
|
-
if (prev.data.stts.length === 1 && newPage > 1) {
|
|
162
|
-
newPage = newPage - 1;
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
...prev,
|
|
166
|
-
data: {
|
|
167
|
-
stts: newStts,
|
|
168
|
-
pagination: {
|
|
169
|
-
...prev.data.pagination,
|
|
170
|
-
total: newTotal,
|
|
171
|
-
page: newPage,
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
});
|
|
176
|
-
},
|
|
177
|
-
[roomId.host],
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
getSTTs(currentPage);
|
|
182
|
-
}, [currentPage, getSTTs]);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
status: state.status as APIStatus,
|
|
186
|
-
stts: state.data.stts,
|
|
187
|
-
pagination: state.data.pagination,
|
|
188
|
-
error: state.error,
|
|
189
|
-
currentPage,
|
|
190
|
-
setCurrentPage,
|
|
191
|
-
deleteTranscript,
|
|
192
|
-
};
|
|
193
|
-
}
|