agora-appbuilder-core 4.1.8-beta.5 → 4.1.8-beta.7
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 +1 -1
- package/template/defaultConfig.js +4 -3
- package/template/global.d.ts +1 -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 +20 -2
- package/template/src/components/controls/useControlPermissionMatrix.tsx +3 -2
- package/template/src/components/recordings/RecordingItemRow.tsx +285 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +89 -26
- package/template/src/components/recordings/TextTrackItemRow.tsx +120 -0
- package/template/src/components/{stt-transcript/STTTranscriptTable.tsx → text-tracks/TextTracksTable.tsx} +56 -50
- package/template/src/components/{stt-transcript/ViewSTTTranscriptModal.tsx → text-tracks/ViewTextTracksModal.tsx} +7 -7
- package/template/src/components/text-tracks/useFetchSTTTranscript.tsx +266 -0
- package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -7
- package/template/src/subComponents/recording/useRecording.tsx +19 -4
- package/template/src/components/stt-transcript/useFetchSTTTranscript.tsx +0 -194
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {View, Text, TouchableOpacity} from 'react-native';
|
|
3
|
+
import IconButtonWithToolTip from '../../atoms/IconButton';
|
|
4
|
+
import Tooltip from '../../atoms/Tooltip';
|
|
5
|
+
import Clipboard from '../../subComponents/Clipboard';
|
|
6
|
+
import Spacer from '../../atoms/Spacer';
|
|
7
|
+
import PlatformWrapper from '../../utils/PlatformWrapper';
|
|
8
|
+
import {FetchSTTTranscriptResponse} from '../text-tracks/useFetchSTTTranscript';
|
|
9
|
+
import {style} from '../common/data-table';
|
|
10
|
+
import ImageIcon from '../../atoms/ImageIcon';
|
|
11
|
+
|
|
12
|
+
interface TextTrackItemRowProps {
|
|
13
|
+
item: FetchSTTTranscriptResponse['stts'][0];
|
|
14
|
+
onTextTrackDownload: (link: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function TextTrackItemRow({
|
|
18
|
+
item,
|
|
19
|
+
onTextTrackDownload,
|
|
20
|
+
}: TextTrackItemRowProps) {
|
|
21
|
+
const textTrackStatus = item.status;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<View style={style.td} key={item.id}>
|
|
25
|
+
{!item.download_url ? (
|
|
26
|
+
<View style={[style.tactions, {marginTop: 0}]}>
|
|
27
|
+
{textTrackStatus === 'STOPPING' ||
|
|
28
|
+
textTrackStatus === 'STARTED' ||
|
|
29
|
+
(textTrackStatus === 'INPROGRESS' && !item?.download_url) ? (
|
|
30
|
+
<Text style={style.placeHolder}>
|
|
31
|
+
{'The link will be generated once the meeting ends'}
|
|
32
|
+
</Text>
|
|
33
|
+
) : (
|
|
34
|
+
<Text style={style.placeHolder}>{'No text-tracks found'}</Text>
|
|
35
|
+
)}
|
|
36
|
+
</View>
|
|
37
|
+
) : item?.download_url?.length > 0 ? (
|
|
38
|
+
<View style={style.tactions}>
|
|
39
|
+
<View>
|
|
40
|
+
{item?.download_url?.map((link: string, i: number) => (
|
|
41
|
+
<View
|
|
42
|
+
key={i}
|
|
43
|
+
style={[
|
|
44
|
+
style.tactions,
|
|
45
|
+
//if stts contains multiple parts then we need to add some space each row
|
|
46
|
+
i >= 1 ? {marginTop: 8} : {},
|
|
47
|
+
]}>
|
|
48
|
+
<View>
|
|
49
|
+
<IconButtonWithToolTip
|
|
50
|
+
hoverEffect={true}
|
|
51
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
52
|
+
containerStyle={style.iconButton}
|
|
53
|
+
iconProps={{
|
|
54
|
+
name: 'download',
|
|
55
|
+
iconType: 'plain',
|
|
56
|
+
iconSize: 20,
|
|
57
|
+
tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
|
|
58
|
+
}}
|
|
59
|
+
onPress={() => {
|
|
60
|
+
onTextTrackDownload && onTextTrackDownload(link);
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
</View>
|
|
64
|
+
<View style={[style.pl10]}>
|
|
65
|
+
<Tooltip
|
|
66
|
+
isClickable
|
|
67
|
+
placement="left"
|
|
68
|
+
toolTipMessage="Link Copied"
|
|
69
|
+
onPress={() => {
|
|
70
|
+
Clipboard.setString(link);
|
|
71
|
+
}}
|
|
72
|
+
toolTipIcon={
|
|
73
|
+
<>
|
|
74
|
+
<ImageIcon
|
|
75
|
+
iconType="plain"
|
|
76
|
+
name="tick-fill"
|
|
77
|
+
tintColor={$config.SEMANTIC_SUCCESS}
|
|
78
|
+
iconSize={20}
|
|
79
|
+
/>
|
|
80
|
+
<Spacer size={8} horizontal={true} />
|
|
81
|
+
</>
|
|
82
|
+
}
|
|
83
|
+
fontSize={12}
|
|
84
|
+
renderContent={() => {
|
|
85
|
+
return (
|
|
86
|
+
<PlatformWrapper>
|
|
87
|
+
{(isHovered: boolean) => (
|
|
88
|
+
<TouchableOpacity
|
|
89
|
+
style={[
|
|
90
|
+
isHovered ? style.iconButtonHoverEffect : {},
|
|
91
|
+
style.iconShareLink,
|
|
92
|
+
]}
|
|
93
|
+
onPress={() => {
|
|
94
|
+
Clipboard.setString(link);
|
|
95
|
+
}}>
|
|
96
|
+
<ImageIcon
|
|
97
|
+
iconType="plain"
|
|
98
|
+
name="copy-link"
|
|
99
|
+
iconSize={20}
|
|
100
|
+
tintColor={$config.SECONDARY_ACTION_COLOR}
|
|
101
|
+
/>
|
|
102
|
+
</TouchableOpacity>
|
|
103
|
+
)}
|
|
104
|
+
</PlatformWrapper>
|
|
105
|
+
);
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
</View>
|
|
109
|
+
</View>
|
|
110
|
+
))}
|
|
111
|
+
</View>
|
|
112
|
+
</View>
|
|
113
|
+
) : (
|
|
114
|
+
<View style={(style.tactions, {marginTop: 0})}>
|
|
115
|
+
<Text style={style.placeHolder}>No text-tracks found</Text>
|
|
116
|
+
</View>
|
|
117
|
+
)}
|
|
118
|
+
</View>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -22,20 +22,24 @@ import PlatformWrapper from '../../utils/PlatformWrapper';
|
|
|
22
22
|
|
|
23
23
|
const headers = ['Date', 'Time', 'Status', 'Actions'];
|
|
24
24
|
|
|
25
|
-
interface
|
|
25
|
+
interface TextTrackItemRowProps {
|
|
26
26
|
item: FetchSTTTranscriptResponse['stts'][0];
|
|
27
27
|
onDeleteAction: (id: string) => void;
|
|
28
28
|
onDownloadAction: (link: string) => void;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function
|
|
31
|
+
function TextTrackItemRow({
|
|
32
|
+
item,
|
|
33
|
+
onDeleteAction,
|
|
34
|
+
onDownloadAction,
|
|
35
|
+
}: TextTrackItemRowProps) {
|
|
32
36
|
const [date, time] = getFormattedDateTime(item.created_at);
|
|
33
|
-
const
|
|
37
|
+
const textTrackStatus = item.status;
|
|
34
38
|
|
|
35
39
|
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
(
|
|
40
|
+
textTrackStatus === 'STOPPING' ||
|
|
41
|
+
textTrackStatus === 'STARTED' ||
|
|
42
|
+
(textTrackStatus === 'INPROGRESS' && !item?.download_url)
|
|
39
43
|
) {
|
|
40
44
|
return (
|
|
41
45
|
<View key={item.id} style={style.pt12}>
|
|
@@ -63,12 +67,14 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
|
|
|
63
67
|
<Text style={style.ttime}>{time}</Text>
|
|
64
68
|
</View>
|
|
65
69
|
<View style={[style.td]}>
|
|
66
|
-
<Text style={style.ttime}>
|
|
70
|
+
<Text style={style.ttime}>
|
|
71
|
+
{capitalizeFirstLetter(textTrackStatus)}
|
|
72
|
+
</Text>
|
|
67
73
|
</View>
|
|
68
74
|
<View style={style.td}>
|
|
69
75
|
{!item.download_url ? (
|
|
70
76
|
<View style={[style.tactions, {marginTop: 0}]}>
|
|
71
|
-
<Text style={style.placeHolder}>{'No
|
|
77
|
+
<Text style={style.placeHolder}>{'No text-tracks found'}</Text>
|
|
72
78
|
</View>
|
|
73
79
|
) : item?.download_url?.length > 0 ? (
|
|
74
80
|
<View style={style.tactions}>
|
|
@@ -165,7 +171,7 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
|
|
|
165
171
|
</View>
|
|
166
172
|
) : (
|
|
167
173
|
<View style={(style.tactions, {marginTop: 0})}>
|
|
168
|
-
<Text style={style.placeHolder}>No
|
|
174
|
+
<Text style={style.placeHolder}>No text-tracks found</Text>
|
|
169
175
|
</View>
|
|
170
176
|
)}
|
|
171
177
|
</View>
|
|
@@ -173,7 +179,7 @@ function STTItemRow({item, onDeleteAction, onDownloadAction}: STTItemRowProps) {
|
|
|
173
179
|
);
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
function
|
|
182
|
+
function EmptyTextTrackState() {
|
|
177
183
|
return (
|
|
178
184
|
<View style={style.infotextContainer}>
|
|
179
185
|
<View>
|
|
@@ -186,49 +192,45 @@ function EmptyTranscriptState() {
|
|
|
186
192
|
</View>
|
|
187
193
|
<View>
|
|
188
194
|
<Text style={[style.infoText, style.pt10, style.pl10]}>
|
|
189
|
-
No
|
|
195
|
+
No text-tracks found for this meeting
|
|
190
196
|
</Text>
|
|
191
197
|
</View>
|
|
192
198
|
</View>
|
|
193
199
|
);
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
function
|
|
202
|
+
function ErrorTextTrackState({message}: {message: string}) {
|
|
197
203
|
return <Text style={[style.ttime, style.pv10, style.ph20]}>{message}</Text>;
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
function
|
|
206
|
+
function TextTracksTable() {
|
|
207
|
+
const {sttState, currentPage, setCurrentPage, deleteTranscript} =
|
|
208
|
+
useFetchSTTTranscript();
|
|
209
|
+
|
|
201
210
|
const {
|
|
202
211
|
status,
|
|
203
|
-
stts,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// id of STT transcript to delete
|
|
212
|
-
const [sttIdToDelete, setSTTIdToDelete] = React.useState<string | undefined>(
|
|
213
|
-
undefined,
|
|
214
|
-
);
|
|
212
|
+
data: {stts, pagination},
|
|
213
|
+
error: fetchTranscriptError,
|
|
214
|
+
} = sttState;
|
|
215
|
+
// id of text-tracj to delete
|
|
216
|
+
const [textTrackIdToDelete, setTextTrackIdToDelete] = React.useState<
|
|
217
|
+
string | undefined
|
|
218
|
+
>(undefined);
|
|
215
219
|
|
|
216
220
|
// message for any download‐error popup
|
|
217
|
-
const [
|
|
218
|
-
string | undefined
|
|
219
|
-
>();
|
|
221
|
+
const [errorSnack, setErrorSnack] = React.useState<string | undefined>();
|
|
220
222
|
|
|
221
223
|
if (status === 'rejected') {
|
|
222
|
-
return <
|
|
224
|
+
return <ErrorTextTrackState message={fetchTranscriptError?.message} />;
|
|
223
225
|
}
|
|
224
226
|
|
|
225
|
-
const
|
|
227
|
+
const onDeleteTextTrackRecord = async (trackId: string) => {
|
|
226
228
|
try {
|
|
227
|
-
await deleteTranscript(
|
|
229
|
+
await deleteTranscript(trackId!);
|
|
228
230
|
} catch (err: any) {
|
|
229
|
-
|
|
231
|
+
setErrorSnack(err.message);
|
|
230
232
|
} finally {
|
|
231
|
-
|
|
233
|
+
setTextTrackIdToDelete(undefined);
|
|
232
234
|
}
|
|
233
235
|
};
|
|
234
236
|
|
|
@@ -240,23 +242,23 @@ function STTTranscriptTable() {
|
|
|
240
242
|
status={status}
|
|
241
243
|
items={stts}
|
|
242
244
|
loadingComponent={
|
|
243
|
-
<Loading background="transparent" text="Fetching
|
|
245
|
+
<Loading background="transparent" text="Fetching text-tracks.." />
|
|
244
246
|
}
|
|
245
247
|
renderRow={item => (
|
|
246
|
-
<
|
|
248
|
+
<TextTrackItemRow
|
|
247
249
|
key={item.id}
|
|
248
250
|
item={item}
|
|
249
251
|
onDeleteAction={id => {
|
|
250
|
-
|
|
252
|
+
setTextTrackIdToDelete(id);
|
|
251
253
|
}}
|
|
252
254
|
onDownloadAction={link => {
|
|
253
255
|
downloadS3Link(link).catch((err: Error) => {
|
|
254
|
-
|
|
256
|
+
setErrorSnack(err.message || 'Download failed');
|
|
255
257
|
});
|
|
256
258
|
}}
|
|
257
259
|
/>
|
|
258
260
|
)}
|
|
259
|
-
emptyComponent={<
|
|
261
|
+
emptyComponent={<EmptyTextTrackState />}
|
|
260
262
|
/>
|
|
261
263
|
<TableFooter
|
|
262
264
|
currentPage={currentPage}
|
|
@@ -264,32 +266,36 @@ function STTTranscriptTable() {
|
|
|
264
266
|
pagination={pagination}
|
|
265
267
|
/>
|
|
266
268
|
</View>
|
|
267
|
-
{
|
|
269
|
+
{textTrackIdToDelete && (
|
|
268
270
|
<GenericPopup
|
|
269
271
|
title="Delete ? "
|
|
270
272
|
variant="error"
|
|
271
|
-
message="Are you sure want to delete the
|
|
272
|
-
visible={!!
|
|
273
|
-
setVisible={() =>
|
|
274
|
-
onConfirm={
|
|
273
|
+
message="Are you sure want to delete the text-track ? This action can't be undone."
|
|
274
|
+
visible={!!textTrackIdToDelete}
|
|
275
|
+
setVisible={() => setTextTrackIdToDelete(undefined)}
|
|
276
|
+
onConfirm={() => {
|
|
277
|
+
const idToDelete = textTrackIdToDelete;
|
|
278
|
+
setTextTrackIdToDelete(undefined);
|
|
279
|
+
onDeleteTextTrackRecord(idToDelete);
|
|
280
|
+
}}
|
|
275
281
|
onCancel={() => {
|
|
276
|
-
|
|
282
|
+
setTextTrackIdToDelete(undefined);
|
|
277
283
|
}}
|
|
278
284
|
/>
|
|
279
285
|
)}
|
|
280
286
|
{/** DOWNLOAD ERROR POPUP **/}
|
|
281
|
-
{
|
|
287
|
+
{errorSnack && (
|
|
282
288
|
<GenericPopup
|
|
283
|
-
title="
|
|
289
|
+
title="Error"
|
|
284
290
|
variant="error"
|
|
285
|
-
message={
|
|
291
|
+
message={errorSnack}
|
|
286
292
|
visible={true}
|
|
287
|
-
setVisible={() =>
|
|
288
|
-
onConfirm={() =>
|
|
293
|
+
setVisible={() => setErrorSnack(undefined)}
|
|
294
|
+
onConfirm={() => setErrorSnack(undefined)}
|
|
289
295
|
/>
|
|
290
296
|
)}
|
|
291
297
|
</>
|
|
292
298
|
);
|
|
293
299
|
}
|
|
294
300
|
|
|
295
|
-
export default
|
|
301
|
+
export default TextTracksTable;
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import React, {SetStateAction, Dispatch} from 'react';
|
|
2
2
|
import {View, StyleSheet} from 'react-native';
|
|
3
3
|
import {useString} from '../../utils/useString';
|
|
4
|
-
import {
|
|
4
|
+
import {textTrackModalTitleIntn} from '../../language/default-labels/videoCallScreenLabels';
|
|
5
5
|
import GenericModal from '../common/GenericModal';
|
|
6
|
-
import
|
|
6
|
+
import TextTracksTable from './TextTracksTable';
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface ViewTextTracksModalProps {
|
|
9
9
|
setModalOpen: Dispatch<SetStateAction<boolean>>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export default function
|
|
12
|
+
export default function ViewTextTracksModal(props: ViewTextTracksModalProps) {
|
|
13
13
|
const {setModalOpen} = props;
|
|
14
14
|
|
|
15
|
-
const
|
|
15
|
+
const textTrackModalTitle = useString(textTrackModalTitleIntn)();
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<GenericModal
|
|
19
19
|
visible={true}
|
|
20
20
|
onRequestClose={() => setModalOpen(false)}
|
|
21
21
|
showCloseIcon={true}
|
|
22
|
-
title={
|
|
22
|
+
title={textTrackModalTitle}
|
|
23
23
|
cancelable={false}
|
|
24
24
|
contentContainerStyle={style.contentContainer}>
|
|
25
25
|
<View style={style.fullBody}>
|
|
26
|
-
<
|
|
26
|
+
<TextTracksTable />
|
|
27
27
|
</View>
|
|
28
28
|
</GenericModal>
|
|
29
29
|
);
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
useEffect(() => {
|
|
123
|
+
getSTTs(currentPage);
|
|
124
|
+
}, [currentPage, getSTTs]);
|
|
125
|
+
|
|
126
|
+
// Delete stts
|
|
127
|
+
const deleteTranscript = useCallback(
|
|
128
|
+
async (id: string) => {
|
|
129
|
+
const reqId = getUniqueID();
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
|
|
132
|
+
const res = await fetch(
|
|
133
|
+
`${
|
|
134
|
+
$config.BACKEND_ENDPOINT
|
|
135
|
+
}/v1/stt-transcript/${id}?passphrase=${encodeURIComponent(
|
|
136
|
+
roomId.host,
|
|
137
|
+
)}`,
|
|
138
|
+
{
|
|
139
|
+
method: 'DELETE',
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
143
|
+
'X-Request-Id': reqId,
|
|
144
|
+
'X-Session-Id': logger.getSessionId(),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
const end = Date.now();
|
|
149
|
+
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
logger.error(
|
|
152
|
+
LogSource.NetworkRest,
|
|
153
|
+
'stt-transcript',
|
|
154
|
+
'Delete transcript failed',
|
|
155
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
156
|
+
);
|
|
157
|
+
throw new Error(`Delete failed (${res.status})`);
|
|
158
|
+
}
|
|
159
|
+
logger.debug(
|
|
160
|
+
LogSource.NetworkRest,
|
|
161
|
+
'stt-transcript',
|
|
162
|
+
'Delete transcript succeeded',
|
|
163
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// optimistic remove from paginated list
|
|
167
|
+
setSttState(prev => {
|
|
168
|
+
// remove the deleted item
|
|
169
|
+
const newStts = prev.data.stts.filter(item => item.id !== id);
|
|
170
|
+
// decrement total count
|
|
171
|
+
const newTotal = Math.max(prev.data.pagination.total - 1, 0);
|
|
172
|
+
let newPage = prev.data.pagination.page;
|
|
173
|
+
if (prev.data.stts.length === 1 && newPage > 1) {
|
|
174
|
+
newPage--;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
...prev,
|
|
178
|
+
data: {
|
|
179
|
+
stts: newStts,
|
|
180
|
+
|
|
181
|
+
pagination: {
|
|
182
|
+
...prev.data.pagination,
|
|
183
|
+
total: newTotal,
|
|
184
|
+
page: newPage,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
[roomId.host, store.token],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
//–– fetch for a given recording ––
|
|
194
|
+
const getSTTsForRecording = useCallback(
|
|
195
|
+
(recordingId: string) => {
|
|
196
|
+
setSttRecState(r => ({...r, status: 'pending', error: null}));
|
|
197
|
+
const reqId = getUniqueID();
|
|
198
|
+
const start = Date.now();
|
|
199
|
+
|
|
200
|
+
fetch(`${$config.BACKEND_ENDPOINT}/v1/recording/stt-transcript`, {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
'Content-Type': 'application/json',
|
|
204
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
205
|
+
'X-Request-Id': reqId,
|
|
206
|
+
'X-Session-Id': logger.getSessionId(),
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
project_id: $config.PROJECT_ID,
|
|
210
|
+
recording_id: recordingId,
|
|
211
|
+
}),
|
|
212
|
+
})
|
|
213
|
+
.then(async res => {
|
|
214
|
+
const json = await res.json();
|
|
215
|
+
const end = Date.now();
|
|
216
|
+
console.log('supriua json', json);
|
|
217
|
+
if (!res.ok) {
|
|
218
|
+
logger.error(
|
|
219
|
+
LogSource.NetworkRest,
|
|
220
|
+
'stt-transcript',
|
|
221
|
+
'Fetch stt-by-recording failed',
|
|
222
|
+
{json, start, end, latency: end - start, requestId: reqId},
|
|
223
|
+
);
|
|
224
|
+
throw new Error(json?.error?.message || res.statusText);
|
|
225
|
+
}
|
|
226
|
+
logger.debug(
|
|
227
|
+
LogSource.NetworkRest,
|
|
228
|
+
'stt-transcript',
|
|
229
|
+
'Fetch stt-by-recording succeeded',
|
|
230
|
+
{json, start, end, latency: end - start, requestId: reqId},
|
|
231
|
+
);
|
|
232
|
+
if (json?.error) {
|
|
233
|
+
logger.debug(
|
|
234
|
+
LogSource.NetworkRest,
|
|
235
|
+
'stt-transcript',
|
|
236
|
+
`No STT records found (code ${json.error.code}): ${json.error.message}`,
|
|
237
|
+
{start, end, latency: end - start, reqId},
|
|
238
|
+
);
|
|
239
|
+
return [];
|
|
240
|
+
} else {
|
|
241
|
+
return json as FetchSTTTranscriptResponse['stts'];
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
.then(stts =>
|
|
245
|
+
setSttRecState({status: 'resolved', data: {stts}, error: null}),
|
|
246
|
+
)
|
|
247
|
+
.catch(err =>
|
|
248
|
+
setSttRecState(r => ({...r, status: 'rejected', error: err})),
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
[store.token],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
// stt list
|
|
256
|
+
sttState,
|
|
257
|
+
getSTTs,
|
|
258
|
+
currentPage,
|
|
259
|
+
setCurrentPage,
|
|
260
|
+
// STT per recording
|
|
261
|
+
sttRecState,
|
|
262
|
+
getSTTsForRecording,
|
|
263
|
+
// delete
|
|
264
|
+
deleteTranscript,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
@@ -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]:
|