agora-appbuilder-core 4.1.8-beta.6 → 4.1.8-beta.8
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/package.json +1 -1
- package/template/src/components/common/data-table.tsx +42 -15
- package/template/src/components/controls/useControlPermissionMatrix.tsx +1 -0
- package/template/src/components/recordings/RecordingItemRow.tsx +285 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +92 -25
- package/template/src/components/recordings/TextTrackItemRow.tsx +120 -0
- package/template/src/components/text-tracks/TextTracksTable.tsx +5 -7
- package/template/src/components/text-tracks/useFetchSTTTranscript.tsx +153 -112
- package/template/src/subComponents/recording/useRecording.tsx +19 -4
package/package.json
CHANGED
|
@@ -77,8 +77,8 @@ const DefaultConfig = {
|
|
|
77
77
|
CHAT_ORG_NAME: '',
|
|
78
78
|
CHAT_APP_NAME: '',
|
|
79
79
|
CHAT_URL: '',
|
|
80
|
-
CLI_VERSION: '3.1.8-beta.
|
|
81
|
-
CORE_VERSION: '4.1.8-beta.
|
|
80
|
+
CLI_VERSION: '3.1.8-beta.8',
|
|
81
|
+
CORE_VERSION: '4.1.8-beta.8',
|
|
82
82
|
DISABLE_LANDSCAPE_MODE: false,
|
|
83
83
|
STT_AUTO_START: false,
|
|
84
84
|
CLOUD_RECORDING_AUTO_START: false,
|
|
@@ -89,7 +89,8 @@ const DefaultConfig = {
|
|
|
89
89
|
AI_LAYOUT: 'LAYOUT_TYPE_1',
|
|
90
90
|
SDK_CODEC: 'vp8',
|
|
91
91
|
ENABLE_WAITING_ROOM_AUTO_APPROVAL: false,
|
|
92
|
-
ENABLE_WAITING_ROOM_AUTO_REQUEST: false
|
|
92
|
+
ENABLE_WAITING_ROOM_AUTO_REQUEST: false,
|
|
93
|
+
ENABLE_TEXT_TRACKS: true,
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
module.exports = DefaultConfig;
|
package/template/global.d.ts
CHANGED
|
@@ -177,6 +177,7 @@ interface ConfigInterface {
|
|
|
177
177
|
SDK_CODEC: string;
|
|
178
178
|
ENABLE_WAITING_ROOM_AUTO_APPROVAL: boolean;
|
|
179
179
|
ENABLE_WAITING_ROOM_AUTO_REQUEST: boolean;
|
|
180
|
+
ENABLE_TEXT_TRACKS: boolean;
|
|
180
181
|
}
|
|
181
182
|
declare var $config: ConfigInterface;
|
|
182
183
|
declare module 'customization' {
|
package/template/package.json
CHANGED
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"agora-extension-beauty-effect": "^1.0.2-beta",
|
|
65
65
|
"agora-extension-virtual-background": "^1.1.3",
|
|
66
66
|
"agora-react-native-rtm": "1.5.1",
|
|
67
|
-
"agora-rtc-sdk-ng": "4.23.
|
|
67
|
+
"agora-rtc-sdk-ng": "4.23.4",
|
|
68
68
|
"agora-rtm-sdk": "1.5.1",
|
|
69
69
|
"buffer": "^6.0.3",
|
|
70
70
|
"electron-log": "4.3.5",
|
|
@@ -19,6 +19,7 @@ interface TableHeaderProps {
|
|
|
19
19
|
rowStyle?: ViewStyle;
|
|
20
20
|
cellStyle?: ViewStyle;
|
|
21
21
|
firstCellStyle?: ViewStyle;
|
|
22
|
+
lastCellStyle?: ViewStyle;
|
|
22
23
|
textStyle?: TextStyle;
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -28,22 +29,27 @@ const TableHeader: React.FC<TableHeaderProps> = ({
|
|
|
28
29
|
rowStyle,
|
|
29
30
|
cellStyle,
|
|
30
31
|
firstCellStyle,
|
|
32
|
+
lastCellStyle,
|
|
31
33
|
textStyle,
|
|
32
34
|
}) => (
|
|
33
35
|
<View style={[style.thead, containerStyle]}>
|
|
34
36
|
<View style={[style.throw, rowStyle]}>
|
|
35
|
-
{columns.map((col, index) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
{columns.map((col, index) => {
|
|
38
|
+
const isFirst = index === 0;
|
|
39
|
+
const isLast = index === (columns.length > 1 ? columns.length - 1 : 0);
|
|
40
|
+
return (
|
|
41
|
+
<View
|
|
42
|
+
key={col}
|
|
43
|
+
style={[
|
|
44
|
+
style.th,
|
|
45
|
+
cellStyle,
|
|
46
|
+
isFirst && firstCellStyle,
|
|
47
|
+
isLast && lastCellStyle,
|
|
48
|
+
]}>
|
|
49
|
+
<Text style={[style.thText, textStyle]}>{col}</Text>
|
|
50
|
+
</View>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
47
53
|
</View>
|
|
48
54
|
</View>
|
|
49
55
|
);
|
|
@@ -151,7 +157,7 @@ const TableFooter: React.FC<TableFooterProps> = ({
|
|
|
151
157
|
|
|
152
158
|
export {TableHeader, TableFooter, TableBody};
|
|
153
159
|
|
|
154
|
-
const style = StyleSheet.create({
|
|
160
|
+
export const style = StyleSheet.create({
|
|
155
161
|
scrollgrow: {
|
|
156
162
|
flexGrow: 1,
|
|
157
163
|
},
|
|
@@ -222,7 +228,7 @@ const style = StyleSheet.create({
|
|
|
222
228
|
flex: 1,
|
|
223
229
|
alignSelf: 'stretch',
|
|
224
230
|
justifyContent: 'center',
|
|
225
|
-
paddingHorizontal: 12,
|
|
231
|
+
// paddingHorizontal: 12,
|
|
226
232
|
},
|
|
227
233
|
thText: {
|
|
228
234
|
color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.medium,
|
|
@@ -249,7 +255,6 @@ const style = StyleSheet.create({
|
|
|
249
255
|
flex: 1,
|
|
250
256
|
alignSelf: 'center',
|
|
251
257
|
justifyContent: 'center',
|
|
252
|
-
// height: 100,
|
|
253
258
|
gap: 10,
|
|
254
259
|
},
|
|
255
260
|
tpreview: {
|
|
@@ -275,6 +280,8 @@ const style = StyleSheet.create({
|
|
|
275
280
|
tactions: {
|
|
276
281
|
display: 'flex',
|
|
277
282
|
flexDirection: 'row',
|
|
283
|
+
alignItems: 'center',
|
|
284
|
+
justifyContent: 'flex-end',
|
|
278
285
|
},
|
|
279
286
|
tlink: {
|
|
280
287
|
color: $config.PRIMARY_ACTION_BRAND_COLOR,
|
|
@@ -382,4 +389,24 @@ const style = StyleSheet.create({
|
|
|
382
389
|
pl15: {
|
|
383
390
|
paddingLeft: 15,
|
|
384
391
|
},
|
|
392
|
+
// icon celles
|
|
393
|
+
tdIconCell: {
|
|
394
|
+
flex: 0,
|
|
395
|
+
flexShrink: 0,
|
|
396
|
+
alignItems: 'flex-start',
|
|
397
|
+
justifyContent: 'center',
|
|
398
|
+
minWidth: 52,
|
|
399
|
+
// paddingRight: 50 + 12,
|
|
400
|
+
},
|
|
401
|
+
thIconCell: {
|
|
402
|
+
flex: 0,
|
|
403
|
+
flexShrink: 0,
|
|
404
|
+
alignSelf: 'stretch',
|
|
405
|
+
justifyContent: 'center',
|
|
406
|
+
minWidth: 50,
|
|
407
|
+
paddingHorizontal: 12,
|
|
408
|
+
},
|
|
409
|
+
alignCellToRight: {
|
|
410
|
+
alignItems: 'flex-end',
|
|
411
|
+
},
|
|
385
412
|
});
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import {View, Text, Linking, TouchableOpacity, StyleSheet} from 'react-native';
|
|
3
|
+
import {downloadRecording, getDuration, getRecordedDateTime} from './utils';
|
|
4
|
+
import IconButtonWithToolTip from '../../atoms/IconButton';
|
|
5
|
+
import Tooltip from '../../atoms/Tooltip';
|
|
6
|
+
import Clipboard from '../../subComponents/Clipboard';
|
|
7
|
+
import Spacer from '../../atoms/Spacer';
|
|
8
|
+
import PlatformWrapper from '../../utils/PlatformWrapper';
|
|
9
|
+
import {useFetchSTTTranscript} from '../text-tracks/useFetchSTTTranscript';
|
|
10
|
+
import {style} from '../common/data-table';
|
|
11
|
+
import {FetchRecordingData} from '../../subComponents/recording/useRecording';
|
|
12
|
+
import ImageIcon from '../../atoms/ImageIcon';
|
|
13
|
+
import TextTrackItemRow from './TextTrackItemRow';
|
|
14
|
+
|
|
15
|
+
interface RecordingItemRowProps {
|
|
16
|
+
item: FetchRecordingData['recordings'][0];
|
|
17
|
+
onDeleteAction: (id: string) => void;
|
|
18
|
+
onTextTrackDownload: (textTrackLink: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export default function RecordingItemRow({
|
|
21
|
+
item,
|
|
22
|
+
onDeleteAction,
|
|
23
|
+
onTextTrackDownload,
|
|
24
|
+
}: RecordingItemRowProps) {
|
|
25
|
+
const [expanded, setIsExpanded] = useState(false);
|
|
26
|
+
|
|
27
|
+
const [date, time] = getRecordedDateTime(item.created_at);
|
|
28
|
+
const recordingStatus = item.status;
|
|
29
|
+
|
|
30
|
+
const {sttRecState, getSTTsForRecording} = useFetchSTTTranscript();
|
|
31
|
+
const {
|
|
32
|
+
status,
|
|
33
|
+
error,
|
|
34
|
+
data: {stts = []},
|
|
35
|
+
} = sttRecState;
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (expanded) {
|
|
39
|
+
if (item.id) {
|
|
40
|
+
getSTTsForRecording(item.id);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, [expanded, item.id, getSTTsForRecording]);
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
recordingStatus === 'STOPPING' ||
|
|
47
|
+
recordingStatus === 'STARTED' ||
|
|
48
|
+
(recordingStatus === 'INPROGRESS' && !item?.download_url)
|
|
49
|
+
) {
|
|
50
|
+
return (
|
|
51
|
+
<View key={item.id} style={style.pt12}>
|
|
52
|
+
<View style={[style.infotextContainer, style.captionContainer]}>
|
|
53
|
+
<ImageIcon
|
|
54
|
+
iconSize={20}
|
|
55
|
+
iconType="plain"
|
|
56
|
+
name="info"
|
|
57
|
+
tintColor={$config.SEMANTIC_NEUTRAL}
|
|
58
|
+
/>
|
|
59
|
+
<Text style={[style.captionText]}>
|
|
60
|
+
Current recording is ongoing. Once it concludes, we'll generate the
|
|
61
|
+
link
|
|
62
|
+
</Text>
|
|
63
|
+
</View>
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Collapsible Row
|
|
69
|
+
return (
|
|
70
|
+
<View>
|
|
71
|
+
{/* ========== PARENT ROW ========== */}
|
|
72
|
+
<View style={style.tbrow} key={item.id}>
|
|
73
|
+
<View style={style.tdIconCell}>
|
|
74
|
+
<IconButtonWithToolTip
|
|
75
|
+
hoverEffect={true}
|
|
76
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
77
|
+
containerStyle={style.iconButton}
|
|
78
|
+
iconProps={{
|
|
79
|
+
name: expanded ? 'arrow-up' : 'arrow-down',
|
|
80
|
+
iconType: 'plain',
|
|
81
|
+
iconSize: 20,
|
|
82
|
+
tintColor: `${$config.FONT_COLOR}`,
|
|
83
|
+
}}
|
|
84
|
+
onPress={() => setIsExpanded(prev => !prev)}
|
|
85
|
+
/>
|
|
86
|
+
</View>
|
|
87
|
+
<View style={[style.td, style.plzero]}>
|
|
88
|
+
<Text style={style.ttime}>
|
|
89
|
+
{date}
|
|
90
|
+
<br />
|
|
91
|
+
<Text style={style.ttime}>{time}</Text>
|
|
92
|
+
</Text>
|
|
93
|
+
</View>
|
|
94
|
+
<View style={[style.td]}>
|
|
95
|
+
<Text style={style.ttime}>
|
|
96
|
+
{getDuration(item.created_at, item.ended_at)}
|
|
97
|
+
</Text>
|
|
98
|
+
</View>
|
|
99
|
+
<View style={style.td}>
|
|
100
|
+
{!item.download_url ? (
|
|
101
|
+
<View style={(style.tactions, {marginTop: 0})}>
|
|
102
|
+
<Text style={style.placeHolder}>{'No recording found'}</Text>
|
|
103
|
+
</View>
|
|
104
|
+
) : item?.download_url?.length > 0 ? (
|
|
105
|
+
<View style={style.tactions}>
|
|
106
|
+
<View>
|
|
107
|
+
{item?.download_url?.map((link: string, i: number) => (
|
|
108
|
+
<View
|
|
109
|
+
style={[
|
|
110
|
+
style.tactions,
|
|
111
|
+
//if recording contains multiple parts then we need to add some space each row
|
|
112
|
+
i >= 1 ? {marginTop: 8} : {},
|
|
113
|
+
]}>
|
|
114
|
+
<View>
|
|
115
|
+
<IconButtonWithToolTip
|
|
116
|
+
hoverEffect={true}
|
|
117
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
118
|
+
containerStyle={style.iconButton}
|
|
119
|
+
iconProps={{
|
|
120
|
+
name: 'download',
|
|
121
|
+
iconType: 'plain',
|
|
122
|
+
iconSize: 20,
|
|
123
|
+
tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
|
|
124
|
+
}}
|
|
125
|
+
onPress={() => {
|
|
126
|
+
downloadRecording(link);
|
|
127
|
+
}}
|
|
128
|
+
/>
|
|
129
|
+
</View>
|
|
130
|
+
<View style={style.pl10}>
|
|
131
|
+
<IconButtonWithToolTip
|
|
132
|
+
hoverEffect={true}
|
|
133
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
134
|
+
containerStyle={style.iconButton}
|
|
135
|
+
iconProps={{
|
|
136
|
+
name: 'link-share',
|
|
137
|
+
iconType: 'plain',
|
|
138
|
+
iconSize: 20,
|
|
139
|
+
tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
|
|
140
|
+
}}
|
|
141
|
+
onPress={async () => {
|
|
142
|
+
if (await Linking.canOpenURL(link)) {
|
|
143
|
+
await Linking.openURL(link);
|
|
144
|
+
}
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
</View>
|
|
148
|
+
<View style={[style.pl10]}>
|
|
149
|
+
<Tooltip
|
|
150
|
+
isClickable
|
|
151
|
+
placement="left"
|
|
152
|
+
toolTipMessage="Link Copied"
|
|
153
|
+
onPress={() => {
|
|
154
|
+
Clipboard.setString(link);
|
|
155
|
+
}}
|
|
156
|
+
toolTipIcon={
|
|
157
|
+
<>
|
|
158
|
+
<ImageIcon
|
|
159
|
+
iconType="plain"
|
|
160
|
+
name="tick-fill"
|
|
161
|
+
tintColor={$config.SEMANTIC_SUCCESS}
|
|
162
|
+
iconSize={20}
|
|
163
|
+
/>
|
|
164
|
+
<Spacer size={8} horizontal={true} />
|
|
165
|
+
</>
|
|
166
|
+
}
|
|
167
|
+
fontSize={12}
|
|
168
|
+
renderContent={() => {
|
|
169
|
+
return (
|
|
170
|
+
<PlatformWrapper>
|
|
171
|
+
{(isHovered: boolean) => (
|
|
172
|
+
<TouchableOpacity
|
|
173
|
+
style={[
|
|
174
|
+
isHovered
|
|
175
|
+
? style.iconButtonHoverEffect
|
|
176
|
+
: {},
|
|
177
|
+
style.iconShareLink,
|
|
178
|
+
]}
|
|
179
|
+
onPress={() => {
|
|
180
|
+
Clipboard.setString(link);
|
|
181
|
+
}}>
|
|
182
|
+
<ImageIcon
|
|
183
|
+
iconType="plain"
|
|
184
|
+
name="copy-link"
|
|
185
|
+
iconSize={20}
|
|
186
|
+
tintColor={$config.SECONDARY_ACTION_COLOR}
|
|
187
|
+
/>
|
|
188
|
+
</TouchableOpacity>
|
|
189
|
+
)}
|
|
190
|
+
</PlatformWrapper>
|
|
191
|
+
);
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
</View>
|
|
195
|
+
</View>
|
|
196
|
+
))}
|
|
197
|
+
</View>
|
|
198
|
+
<View style={[style.pl10]}>
|
|
199
|
+
<IconButtonWithToolTip
|
|
200
|
+
hoverEffect={true}
|
|
201
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
202
|
+
containerStyle={style.iconButton}
|
|
203
|
+
iconProps={{
|
|
204
|
+
name: 'delete',
|
|
205
|
+
iconType: 'plain',
|
|
206
|
+
iconSize: 20,
|
|
207
|
+
tintColor: `${$config.SEMANTIC_ERROR}`,
|
|
208
|
+
}}
|
|
209
|
+
onPress={() => {
|
|
210
|
+
onDeleteAction && onDeleteAction(item.id);
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
</View>
|
|
214
|
+
</View>
|
|
215
|
+
) : (
|
|
216
|
+
<View style={(style.tactions, {marginTop: 0})}>
|
|
217
|
+
<Text style={style.placeHolder}>No recordings found</Text>
|
|
218
|
+
</View>
|
|
219
|
+
)}
|
|
220
|
+
</View>
|
|
221
|
+
</View>
|
|
222
|
+
{/* ========== CHILDREN ROW ========== */}
|
|
223
|
+
{expanded && (
|
|
224
|
+
<View style={expanedStyles.expandedContainer}>
|
|
225
|
+
<View>
|
|
226
|
+
<Text style={expanedStyles.expandedHeaderText}>Text-tracks</Text>
|
|
227
|
+
</View>
|
|
228
|
+
<View style={expanedStyles.expandedHeaderBody}>
|
|
229
|
+
{status === 'idle' || status === 'pending' ? (
|
|
230
|
+
<Text style={style.ttime}>Fetching text-tracks....</Text>
|
|
231
|
+
) : status === 'rejected' ? (
|
|
232
|
+
<Text style={style.ttime}>
|
|
233
|
+
{error?.message ||
|
|
234
|
+
'There was an error while fetching the text-tracks'}
|
|
235
|
+
</Text>
|
|
236
|
+
) : status === 'resolved' && stts?.length === 0 ? (
|
|
237
|
+
<Text style={style.ttime}>
|
|
238
|
+
There are no text-tracks's for this recording
|
|
239
|
+
</Text>
|
|
240
|
+
) : (
|
|
241
|
+
<>
|
|
242
|
+
<Text style={style.ttime}>Found {stts.length} text tracks</Text>
|
|
243
|
+
<View>
|
|
244
|
+
{stts.map(item => (
|
|
245
|
+
<TextTrackItemRow
|
|
246
|
+
key={item.id}
|
|
247
|
+
item={item}
|
|
248
|
+
onTextTrackDownload={onTextTrackDownload}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</View>
|
|
252
|
+
</>
|
|
253
|
+
)}
|
|
254
|
+
</View>
|
|
255
|
+
</View>
|
|
256
|
+
)}
|
|
257
|
+
</View>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const expanedStyles = StyleSheet.create({
|
|
262
|
+
expandedContainer: {
|
|
263
|
+
display: 'flex',
|
|
264
|
+
flexDirection: 'column',
|
|
265
|
+
gap: 5,
|
|
266
|
+
color: $config.FONT_COLOR,
|
|
267
|
+
borderColor: $config.CARD_LAYER_3_COLOR,
|
|
268
|
+
backgroundColor: $config.CARD_LAYER_2_COLOR,
|
|
269
|
+
paddingHorizontal: 12,
|
|
270
|
+
paddingVertical: 15,
|
|
271
|
+
borderRadius: 5,
|
|
272
|
+
},
|
|
273
|
+
expandedHeaderText: {
|
|
274
|
+
fontSize: 15,
|
|
275
|
+
lineHeight: 32,
|
|
276
|
+
fontWeight: '500',
|
|
277
|
+
color: $config.FONT_COLOR,
|
|
278
|
+
},
|
|
279
|
+
expandedHeaderBody: {
|
|
280
|
+
display: 'flex',
|
|
281
|
+
flexDirection: 'row',
|
|
282
|
+
justifyContent: 'space-between',
|
|
283
|
+
alignItems: 'flex-start',
|
|
284
|
+
},
|
|
285
|
+
});
|
|
@@ -1,30 +1,65 @@
|
|
|
1
1
|
import React, {useState, useEffect} from 'react';
|
|
2
2
|
import {View, Text} from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
APIStatus,
|
|
5
|
+
FetchRecordingData,
|
|
6
|
+
useRecording,
|
|
7
|
+
} from '../../subComponents/recording/useRecording';
|
|
6
8
|
import events from '../../rtm-events-api';
|
|
7
9
|
import {EventNames} from '../../rtm-events';
|
|
10
|
+
import {style, TableBody, TableHeader} from '../common/data-table';
|
|
11
|
+
import Loading from '../../subComponents/Loading';
|
|
12
|
+
import ImageIcon from '../../atoms/ImageIcon';
|
|
13
|
+
import RecordingItemRow from './RecordingItemRow';
|
|
14
|
+
import GenericPopup from '../common/GenericPopup';
|
|
15
|
+
import {downloadS3Link} from '../../utils/common';
|
|
16
|
+
|
|
17
|
+
function EmptyRecordingState() {
|
|
18
|
+
return (
|
|
19
|
+
<View style={style.infotextContainer}>
|
|
20
|
+
<View>
|
|
21
|
+
<ImageIcon
|
|
22
|
+
iconType="plain"
|
|
23
|
+
name="info"
|
|
24
|
+
tintColor={'#777777'}
|
|
25
|
+
iconSize={32}
|
|
26
|
+
/>
|
|
27
|
+
</View>
|
|
28
|
+
<View>
|
|
29
|
+
<Text style={[style.infoText, style.pt10, style.pl10]}>
|
|
30
|
+
No recording found for this meeting
|
|
31
|
+
</Text>
|
|
32
|
+
</View>
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const headers = ['', 'Date/Time', 'Duration', 'Actions'];
|
|
38
|
+
const defaultPageNumber = 1;
|
|
8
39
|
|
|
9
40
|
function RecordingsDateTable(props) {
|
|
10
|
-
const [state, setState] = React.useState
|
|
41
|
+
const [state, setState] = React.useState<{
|
|
42
|
+
status: APIStatus;
|
|
43
|
+
data: {
|
|
44
|
+
recordings: FetchRecordingData['recordings'];
|
|
45
|
+
pagination: FetchRecordingData['pagination'];
|
|
46
|
+
};
|
|
47
|
+
error: Error;
|
|
48
|
+
}>({
|
|
11
49
|
status: 'idle',
|
|
12
50
|
data: {
|
|
13
|
-
pagination: {},
|
|
14
51
|
recordings: [],
|
|
52
|
+
pagination: {total: 0, limit: 10, page: defaultPageNumber},
|
|
15
53
|
},
|
|
16
54
|
error: null,
|
|
17
55
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
data: {pagination, recordings},
|
|
21
|
-
error,
|
|
22
|
-
} = state;
|
|
56
|
+
|
|
57
|
+
const [currentPage, setCurrentPage] = useState(defaultPageNumber);
|
|
23
58
|
|
|
24
59
|
const {fetchRecordings} = useRecording();
|
|
25
60
|
|
|
26
|
-
|
|
27
|
-
const [
|
|
61
|
+
// message for any download‐error popup
|
|
62
|
+
const [errorSnack, setErrorSnack] = React.useState<string | undefined>();
|
|
28
63
|
|
|
29
64
|
const onRecordingDeleteCallback = () => {
|
|
30
65
|
setCurrentPage(defaultPageNumber);
|
|
@@ -38,7 +73,7 @@ function RecordingsDateTable(props) {
|
|
|
38
73
|
};
|
|
39
74
|
}, []);
|
|
40
75
|
|
|
41
|
-
const getRecordings = pageNumber => {
|
|
76
|
+
const getRecordings = (pageNumber: number) => {
|
|
42
77
|
setState(prev => ({...prev, status: 'pending'}));
|
|
43
78
|
fetchRecordings(pageNumber).then(
|
|
44
79
|
response =>
|
|
@@ -47,8 +82,13 @@ function RecordingsDateTable(props) {
|
|
|
47
82
|
status: 'resolved',
|
|
48
83
|
data: {
|
|
49
84
|
recordings: response?.recordings || [],
|
|
50
|
-
pagination: response?.pagination || {
|
|
85
|
+
pagination: response?.pagination || {
|
|
86
|
+
total: 0,
|
|
87
|
+
limit: 10,
|
|
88
|
+
page: defaultPageNumber,
|
|
89
|
+
},
|
|
51
90
|
},
|
|
91
|
+
error: null,
|
|
52
92
|
})),
|
|
53
93
|
error => setState(prev => ({...prev, status: 'rejected', error})),
|
|
54
94
|
);
|
|
@@ -58,26 +98,53 @@ function RecordingsDateTable(props) {
|
|
|
58
98
|
getRecordings(currentPage);
|
|
59
99
|
}, [currentPage]);
|
|
60
100
|
|
|
61
|
-
if (status === 'rejected') {
|
|
101
|
+
if (state.status === 'rejected') {
|
|
62
102
|
return (
|
|
63
103
|
<Text style={[style.ttime, style.pv10, style.ph20]}>
|
|
64
|
-
{error?.message}
|
|
104
|
+
{state.error?.message}
|
|
65
105
|
</Text>
|
|
66
106
|
);
|
|
67
107
|
}
|
|
108
|
+
const onTextTrackDownload = (textTrackLink: string) => {
|
|
109
|
+
downloadS3Link(textTrackLink).catch((err: Error) => {
|
|
110
|
+
setErrorSnack(err.message || 'Download failed');
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
68
114
|
return (
|
|
69
115
|
<View style={style.ttable}>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
onDeleteAction={props?.onDeleteAction}
|
|
116
|
+
<TableHeader
|
|
117
|
+
columns={headers}
|
|
118
|
+
firstCellStyle={style.thIconCell}
|
|
119
|
+
lastCellStyle={style.alignCellToRight}
|
|
75
120
|
/>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
<TableBody
|
|
122
|
+
status={state.status}
|
|
123
|
+
items={state.data.recordings}
|
|
124
|
+
loadingComponent={
|
|
125
|
+
<Loading background="transparent" text="Fetching recordingss.." />
|
|
126
|
+
}
|
|
127
|
+
renderRow={item => (
|
|
128
|
+
<RecordingItemRow
|
|
129
|
+
key={item.id}
|
|
130
|
+
item={item}
|
|
131
|
+
onDeleteAction={props?.onDeleteAction}
|
|
132
|
+
onTextTrackDownload={onTextTrackDownload}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
emptyComponent={<EmptyRecordingState />}
|
|
80
136
|
/>
|
|
137
|
+
{/** ERROR POPUP **/}
|
|
138
|
+
{errorSnack && (
|
|
139
|
+
<GenericPopup
|
|
140
|
+
title="Error"
|
|
141
|
+
variant="error"
|
|
142
|
+
message={errorSnack}
|
|
143
|
+
visible={true}
|
|
144
|
+
setVisible={() => setErrorSnack(undefined)}
|
|
145
|
+
onConfirm={() => setErrorSnack(undefined)}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
81
148
|
</View>
|
|
82
149
|
);
|
|
83
150
|
}
|
|
@@ -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
|
+
}
|
|
@@ -204,16 +204,14 @@ function ErrorTextTrackState({message}: {message: string}) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
function TextTracksTable() {
|
|
207
|
+
const {sttState, currentPage, setCurrentPage, deleteTranscript} =
|
|
208
|
+
useFetchSTTTranscript();
|
|
209
|
+
|
|
207
210
|
const {
|
|
208
211
|
status,
|
|
209
|
-
stts,
|
|
210
|
-
pagination,
|
|
212
|
+
data: {stts, pagination},
|
|
211
213
|
error: fetchTranscriptError,
|
|
212
|
-
|
|
213
|
-
setCurrentPage,
|
|
214
|
-
deleteTranscript,
|
|
215
|
-
} = useFetchSTTTranscript();
|
|
216
|
-
|
|
214
|
+
} = sttState;
|
|
217
215
|
// id of text-tracj to delete
|
|
218
216
|
const [textTrackIdToDelete, setTextTrackIdToDelete] = React.useState<
|
|
219
217
|
string | undefined
|
|
@@ -5,11 +5,7 @@ import getUniqueID from '../../utils/getUniqueID';
|
|
|
5
5
|
import {logger, LogSource} from '../../logger/AppBuilderLogger';
|
|
6
6
|
|
|
7
7
|
export interface FetchSTTTranscriptResponse {
|
|
8
|
-
pagination: {
|
|
9
|
-
limit: number;
|
|
10
|
-
total: number;
|
|
11
|
-
page: number;
|
|
12
|
-
};
|
|
8
|
+
pagination: {limit: number; total: number; page: number};
|
|
13
9
|
stts: {
|
|
14
10
|
id: string;
|
|
15
11
|
download_url: string[];
|
|
@@ -23,118 +19,114 @@ export interface FetchSTTTranscriptResponse {
|
|
|
23
19
|
|
|
24
20
|
export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
|
|
25
21
|
|
|
26
|
-
export function useFetchSTTTranscript(
|
|
22
|
+
export function useFetchSTTTranscript() {
|
|
27
23
|
const {
|
|
28
24
|
data: {roomId},
|
|
29
25
|
} = useRoomInfo();
|
|
30
26
|
const {store} = useContext(StorageContext);
|
|
27
|
+
|
|
31
28
|
const [currentPage, setCurrentPage] = useState(1);
|
|
32
29
|
|
|
33
|
-
const [
|
|
30
|
+
const [sttState, setSttState] = useState<{
|
|
34
31
|
status: APIStatus;
|
|
35
32
|
data: {
|
|
36
33
|
stts: FetchSTTTranscriptResponse['stts'];
|
|
37
34
|
pagination: FetchSTTTranscriptResponse['pagination'];
|
|
38
35
|
};
|
|
39
|
-
error: Error;
|
|
36
|
+
error: Error | null;
|
|
40
37
|
}>({
|
|
41
38
|
status: 'idle',
|
|
42
|
-
data: {stts: [], pagination: {total: 0, limit:
|
|
39
|
+
data: {stts: [], pagination: {total: 0, limit: 10, page: 1}},
|
|
43
40
|
error: null,
|
|
44
41
|
});
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
});
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const res = await fetch(
|
|
57
|
-
`${$config.BACKEND_ENDPOINT}/v1/stt-transcript`,
|
|
58
|
-
{
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: {
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
63
|
-
'X-Request-Id': requestId,
|
|
64
|
-
'X-Session-Id': logger.getSessionId(),
|
|
65
|
-
},
|
|
66
|
-
body: JSON.stringify({
|
|
67
|
-
passphrase: roomId.host,
|
|
68
|
-
limit: defaultLimit,
|
|
69
|
-
page,
|
|
70
|
-
}),
|
|
71
|
-
},
|
|
72
|
-
);
|
|
73
|
-
const json = await res.json();
|
|
74
|
-
const end = Date.now();
|
|
56
|
+
const getSTTs = useCallback(
|
|
57
|
+
(page: number) => {
|
|
58
|
+
setSttState(s => ({...s, status: 'pending', error: null}));
|
|
59
|
+
const reqId = getUniqueID();
|
|
60
|
+
const start = Date.now();
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
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(
|
|
78
95
|
LogSource.NetworkRest,
|
|
79
96
|
'stt-transcript',
|
|
80
|
-
'
|
|
97
|
+
'Fetch STT transcripts succeeded',
|
|
81
98
|
{
|
|
82
99
|
json,
|
|
83
100
|
start,
|
|
84
101
|
end,
|
|
85
102
|
latency: end - start,
|
|
86
|
-
requestId,
|
|
103
|
+
requestId: reqId,
|
|
87
104
|
},
|
|
88
105
|
);
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
LogSource.NetworkRest,
|
|
94
|
-
'stt-transcript',
|
|
95
|
-
'Fetched STT transcripts',
|
|
96
|
-
{
|
|
97
|
-
json,
|
|
98
|
-
start,
|
|
99
|
-
end,
|
|
100
|
-
latency: end - start,
|
|
101
|
-
requestId,
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
return json;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
return Promise.reject(err);
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
[roomId.host, store.token, defaultLimit],
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
const getSTTs = useCallback(
|
|
113
|
-
(page: number) => {
|
|
114
|
-
setState(s => ({...s, status: 'pending'}));
|
|
115
|
-
fetchStts(page).then(
|
|
116
|
-
data =>
|
|
117
|
-
setState({
|
|
106
|
+
return json as FetchSTTTranscriptResponse;
|
|
107
|
+
})
|
|
108
|
+
.then(({stts = [], pagination = {total: 0, limit: 10, page}}) => {
|
|
109
|
+
setSttState({
|
|
118
110
|
status: 'resolved',
|
|
119
|
-
data: {
|
|
120
|
-
stts: data.stts || [],
|
|
121
|
-
pagination: data.pagination || {
|
|
122
|
-
total: 0,
|
|
123
|
-
limit: defaultLimit,
|
|
124
|
-
page: 1,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
111
|
+
data: {stts, pagination},
|
|
127
112
|
error: null,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
});
|
|
114
|
+
})
|
|
115
|
+
.catch(err => {
|
|
116
|
+
setSttState(s => ({...s, status: 'rejected', error: err}));
|
|
117
|
+
});
|
|
131
118
|
},
|
|
132
|
-
[
|
|
119
|
+
[roomId.host, store.token],
|
|
133
120
|
);
|
|
134
121
|
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
getSTTs(currentPage);
|
|
124
|
+
}, [currentPage, getSTTs]);
|
|
125
|
+
|
|
126
|
+
// Delete stts
|
|
135
127
|
const deleteTranscript = useCallback(
|
|
136
128
|
async (id: string) => {
|
|
137
|
-
const
|
|
129
|
+
const reqId = getUniqueID();
|
|
138
130
|
const start = Date.now();
|
|
139
131
|
|
|
140
132
|
const res = await fetch(
|
|
@@ -148,7 +140,7 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
|
|
|
148
140
|
headers: {
|
|
149
141
|
'Content-Type': 'application/json',
|
|
150
142
|
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
151
|
-
'X-Request-Id':
|
|
143
|
+
'X-Request-Id': reqId,
|
|
152
144
|
'X-Session-Id': logger.getSessionId(),
|
|
153
145
|
},
|
|
154
146
|
},
|
|
@@ -159,44 +151,33 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
|
|
|
159
151
|
logger.error(
|
|
160
152
|
LogSource.NetworkRest,
|
|
161
153
|
'stt-transcript',
|
|
162
|
-
'
|
|
163
|
-
{
|
|
164
|
-
json: '',
|
|
165
|
-
start,
|
|
166
|
-
end,
|
|
167
|
-
latency: end - start,
|
|
168
|
-
requestId,
|
|
169
|
-
},
|
|
154
|
+
'Delete transcript failed',
|
|
155
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
170
156
|
);
|
|
171
157
|
throw new Error(`Delete failed (${res.status})`);
|
|
172
158
|
}
|
|
173
159
|
logger.debug(
|
|
174
160
|
LogSource.NetworkRest,
|
|
175
161
|
'stt-transcript',
|
|
176
|
-
'
|
|
177
|
-
{
|
|
178
|
-
json: '',
|
|
179
|
-
start,
|
|
180
|
-
end,
|
|
181
|
-
latency: end - start,
|
|
182
|
-
requestId,
|
|
183
|
-
},
|
|
162
|
+
'Delete transcript succeeded',
|
|
163
|
+
{start, end, latency: end - start, requestId: reqId},
|
|
184
164
|
);
|
|
185
|
-
|
|
186
|
-
|
|
165
|
+
|
|
166
|
+
// optimistic remove from paginated list
|
|
167
|
+
setSttState(prev => {
|
|
187
168
|
// remove the deleted item
|
|
188
169
|
const newStts = prev.data.stts.filter(item => item.id !== id);
|
|
189
170
|
// decrement total count
|
|
190
171
|
const newTotal = Math.max(prev.data.pagination.total - 1, 0);
|
|
191
|
-
// if we just removed the *last* item on this page, go back a page
|
|
192
172
|
let newPage = prev.data.pagination.page;
|
|
193
173
|
if (prev.data.stts.length === 1 && newPage > 1) {
|
|
194
|
-
newPage
|
|
174
|
+
newPage--;
|
|
195
175
|
}
|
|
196
176
|
return {
|
|
197
177
|
...prev,
|
|
198
178
|
data: {
|
|
199
179
|
stts: newStts,
|
|
180
|
+
|
|
200
181
|
pagination: {
|
|
201
182
|
...prev.data.pagination,
|
|
202
183
|
total: newTotal,
|
|
@@ -206,20 +187,80 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
|
|
|
206
187
|
};
|
|
207
188
|
});
|
|
208
189
|
},
|
|
209
|
-
[roomId.host, store
|
|
190
|
+
[roomId.host, store.token],
|
|
210
191
|
);
|
|
211
192
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
);
|
|
215
253
|
|
|
216
254
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
error: state.error,
|
|
255
|
+
// stt list
|
|
256
|
+
sttState,
|
|
257
|
+
getSTTs,
|
|
221
258
|
currentPage,
|
|
222
259
|
setCurrentPage,
|
|
260
|
+
// STT per recording
|
|
261
|
+
sttRecState,
|
|
262
|
+
getSTTsForRecording,
|
|
263
|
+
// delete
|
|
223
264
|
deleteTranscript,
|
|
224
265
|
};
|
|
225
266
|
}
|
|
@@ -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
|
|