diff --git a/audio-livecast.config.json b/audio-livecast.config.json
index 2e4c58048..8e5243c0a 100644
--- a/audio-livecast.config.json
+++ b/audio-livecast.config.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/audio-livecast.config.light.json b/audio-livecast.config.light.json
index 4b0bfb56c..6881b77bf 100644
--- a/audio-livecast.config.light.json
+++ b/audio-livecast.config.light.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/config.json b/config.json
index 5cafb4c44..678699308 100644
--- a/config.json
+++ b/config.json
@@ -98,5 +98,7 @@
"ENABLE_SPOTLIGHT": false,
"AUTO_CONNECT_RTM": false,
"ENABLE_CONVERSATIONAL_AI": false,
- "CUSTOMIZE_AGENT": false
+ "CUSTOMIZE_AGENT": false,
+ "ENABLE_TEXT_TRACKS": true,
+ "PLAY_REMOTE_AUDIO": false
}
diff --git a/config.light.json b/config.light.json
index cea0d03d5..eae16814d 100644
--- a/config.light.json
+++ b/config.light.json
@@ -96,5 +96,6 @@
"ENABLE_SPOTLIGHT": false,
"AUTO_CONNECT_RTM": false,
"ENABLE_WAITING_ROOM_AUTO_APPROVAL": true,
- "ENABLE_WAITING_ROOM_AUTO_REQUEST": true
+ "ENABLE_WAITING_ROOM_AUTO_REQUEST": true,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/live-streaming.config.json b/live-streaming.config.json
index 4af2cf44d..92b69ea33 100644
--- a/live-streaming.config.json
+++ b/live-streaming.config.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/live-streaming.config.light.json b/live-streaming.config.light.json
index 1f90ba71e..e93a3e2f0 100644
--- a/live-streaming.config.light.json
+++ b/live-streaming.config.light.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/template/bridge/rtc/webNg/RtcEngine.ts b/template/bridge/rtc/webNg/RtcEngine.ts
index 761910582..78ea9cfbf 100644
--- a/template/bridge/rtc/webNg/RtcEngine.ts
+++ b/template/bridge/rtc/webNg/RtcEngine.ts
@@ -752,8 +752,11 @@ export default class RtcEngine {
// If the subscribed track is an audio track
if (mediaType === 'audio') {
const audioTrack = user.audioTrack;
- // Play the audio
- audioTrack?.play();
+ if ($config.PLAY_REMOTE_AUDIO) {
+ // Play the audio
+ audioTrack?.play();
+ }
+
this.remoteStreams.set(user.uid, {
...this.remoteStreams.get(user.uid),
audio: audioTrack,
diff --git a/template/defaultConfig.js b/template/defaultConfig.js
index c0fea119c..fbd5add68 100644
--- a/template/defaultConfig.js
+++ b/template/defaultConfig.js
@@ -89,7 +89,9 @@ const DefaultConfig = {
AI_LAYOUT: 'LAYOUT_TYPE_1',
SDK_CODEC: 'vp8',
ENABLE_WAITING_ROOM_AUTO_APPROVAL: false,
- ENABLE_WAITING_ROOM_AUTO_REQUEST: false
+ ENABLE_WAITING_ROOM_AUTO_REQUEST: false,
+ ENABLE_TEXT_TRACKS: true,
+ PLAY_REMOTE_AUDIO: true,
};
module.exports = DefaultConfig;
diff --git a/template/global.d.ts b/template/global.d.ts
index d20a174ab..aaa966ff9 100644
--- a/template/global.d.ts
+++ b/template/global.d.ts
@@ -177,6 +177,8 @@ interface ConfigInterface {
SDK_CODEC: string;
ENABLE_WAITING_ROOM_AUTO_APPROVAL: boolean;
ENABLE_WAITING_ROOM_AUTO_REQUEST: boolean;
+ ENABLE_TEXT_TRACKS: boolean;
+ PLAY_REMOTE_AUDIO: boolean;
}
declare var $config: ConfigInterface;
declare module 'customization' {
diff --git a/template/src/components/Controls.tsx b/template/src/components/Controls.tsx
index fbedf9b18..0828af9e2 100644
--- a/template/src/components/Controls.tsx
+++ b/template/src/components/Controls.tsx
@@ -817,22 +817,22 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
}
// 13. Text-tracks to download
- // const canAccessAllTextTracks =
- // useControlPermissionMatrix('viewAllTextTracks');
-
- // if (canAccessAllTextTracks) {
- // actionMenuitems.push({
- // componentName: 'view-all-text-tracks',
- // order: 13,
- // icon: 'transcript',
- // iconColor: $config.SECONDARY_ACTION_COLOR,
- // textColor: $config.FONT_COLOR,
- // title: viewTextTracksLabel,
- // onPress: () => {
- // toggleTextTrackModal();
- // },
- // });
- // }
+ const canAccessAllTextTracks =
+ useControlPermissionMatrix('viewAllTextTracks');
+
+ if (canAccessAllTextTracks) {
+ actionMenuitems.push({
+ componentName: 'view-all-text-tracks',
+ order: 13,
+ icon: 'transcript',
+ iconColor: $config.SECONDARY_ACTION_COLOR,
+ textColor: $config.FONT_COLOR,
+ title: viewTextTracksLabel,
+ onPress: () => {
+ toggleTextTrackModal();
+ },
+ });
+ }
useEffect(() => {
if (isHovered) {
@@ -980,11 +980,11 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
)}
>
)}
- {/* {canAccessAllTextTracks && isTextTrackModalOpen ? (
+ {canAccessAllTextTracks && isTextTrackModalOpen ? (
) : (
<>>
- )} */}
+ )}
= ({
rowStyle,
cellStyle,
firstCellStyle,
+ lastCellStyle,
textStyle,
}) => (
- {columns.map((col, index) => (
-
- {col}
-
- ))}
+ {columns.map((col, index) => {
+ const isFirst = index === 0;
+ const isLast = index === (columns.length > 1 ? columns.length - 1 : 0);
+ return (
+
+ {col}
+
+ );
+ })}
);
@@ -151,7 +157,7 @@ const TableFooter: React.FC = ({
export {TableHeader, TableFooter, TableBody};
-const style = StyleSheet.create({
+export const style = StyleSheet.create({
scrollgrow: {
flexGrow: 1,
},
@@ -222,7 +228,7 @@ const style = StyleSheet.create({
flex: 1,
alignSelf: 'stretch',
justifyContent: 'center',
- paddingHorizontal: 12,
+ // paddingHorizontal: 12,
},
thText: {
color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.medium,
@@ -249,7 +255,6 @@ const style = StyleSheet.create({
flex: 1,
alignSelf: 'center',
justifyContent: 'center',
- // height: 100,
gap: 10,
},
tpreview: {
@@ -275,6 +280,8 @@ const style = StyleSheet.create({
tactions: {
display: 'flex',
flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
},
tlink: {
color: $config.PRIMARY_ACTION_BRAND_COLOR,
@@ -382,4 +389,24 @@ const style = StyleSheet.create({
pl15: {
paddingLeft: 15,
},
+ // icon celles
+ tdIconCell: {
+ flex: 0,
+ flexShrink: 0,
+ alignItems: 'flex-start',
+ justifyContent: 'center',
+ minWidth: 52,
+ // paddingRight: 50 + 12,
+ },
+ thIconCell: {
+ flex: 0,
+ flexShrink: 0,
+ alignSelf: 'stretch',
+ justifyContent: 'center',
+ minWidth: 50,
+ paddingHorizontal: 12,
+ },
+ alignCellToRight: {
+ alignItems: 'flex-end',
+ },
});
diff --git a/template/src/components/controls/useControlPermissionMatrix.tsx b/template/src/components/controls/useControlPermissionMatrix.tsx
index 568fb5fde..2daa52cf0 100644
--- a/template/src/components/controls/useControlPermissionMatrix.tsx
+++ b/template/src/components/controls/useControlPermissionMatrix.tsx
@@ -40,6 +40,7 @@ export const controlPermissionMatrix: Record<
isHost &&
$config.ENABLE_STT &&
$config.ENABLE_MEETING_TRANSCRIPT &&
+ $config.ENABLE_TEXT_TRACKS &&
isWeb(),
};
diff --git a/template/src/components/recordings/RecordingItemRow.tsx b/template/src/components/recordings/RecordingItemRow.tsx
new file mode 100644
index 000000000..ba4841321
--- /dev/null
+++ b/template/src/components/recordings/RecordingItemRow.tsx
@@ -0,0 +1,289 @@
+import React, {useEffect, useState} from 'react';
+import {View, Text, Linking, TouchableOpacity, StyleSheet} from 'react-native';
+import {downloadRecording, getDuration, getRecordedDateTime} from './utils';
+import IconButtonWithToolTip from '../../atoms/IconButton';
+import Tooltip from '../../atoms/Tooltip';
+import Clipboard from '../../subComponents/Clipboard';
+import Spacer from '../../atoms/Spacer';
+import PlatformWrapper from '../../utils/PlatformWrapper';
+import {useFetchSTTTranscript} from '../text-tracks/useFetchSTTTranscript';
+import {style} from '../common/data-table';
+import {FetchRecordingData} from '../../subComponents/recording/useRecording';
+import ImageIcon from '../../atoms/ImageIcon';
+import TextTrackItemRow from './TextTrackItemRow';
+
+interface RecordingItemRowProps {
+ item: FetchRecordingData['recordings'][0];
+ onDeleteAction: (id: string) => void;
+ onTextTrackDownload: (textTrackLink: string) => void;
+ showTextTracks: boolean;
+}
+export default function RecordingItemRow({
+ item,
+ onDeleteAction,
+ onTextTrackDownload,
+ showTextTracks = false,
+}: RecordingItemRowProps) {
+ const [expanded, setIsExpanded] = useState(false);
+
+ const [date, time] = getRecordedDateTime(item.created_at);
+ const recordingStatus = item.status;
+
+ const {sttRecState, getSTTsForRecording} = useFetchSTTTranscript();
+ const {
+ status,
+ error,
+ data: {stts = []},
+ } = sttRecState;
+
+ useEffect(() => {
+ if (expanded) {
+ if (item.id) {
+ getSTTsForRecording(item.id);
+ }
+ }
+ }, [expanded, item.id, getSTTsForRecording]);
+
+ if (
+ recordingStatus === 'STOPPING' ||
+ recordingStatus === 'STARTED' ||
+ (recordingStatus === 'INPROGRESS' && !item?.download_url)
+ ) {
+ return (
+
+
+
+
+ Current recording is ongoing. Once it concludes, we'll generate the
+ link
+
+
+
+ );
+ }
+
+ // Collapsible Row
+ return (
+
+ {/* ========== PARENT ROW ========== */}
+
+ {showTextTracks && (
+
+ setIsExpanded(prev => !prev)}
+ />
+
+ )}
+
+
+ {date}
+
+ {time}
+
+
+
+
+ {getDuration(item.created_at, item.ended_at)}
+
+
+
+ {!item.download_url ? (
+
+ {'No recording found'}
+
+ ) : item?.download_url?.length > 0 ? (
+
+
+ {item?.download_url?.map((link: string, i: number) => (
+ = 1 ? {marginTop: 8} : {},
+ ]}>
+
+ {
+ downloadRecording(link);
+ }}
+ />
+
+
+ {
+ if (await Linking.canOpenURL(link)) {
+ await Linking.openURL(link);
+ }
+ }}
+ />
+
+
+ {
+ Clipboard.setString(link);
+ }}
+ toolTipIcon={
+ <>
+
+
+ >
+ }
+ fontSize={12}
+ renderContent={() => {
+ return (
+
+ {(isHovered: boolean) => (
+ {
+ Clipboard.setString(link);
+ }}>
+
+
+ )}
+
+ );
+ }}
+ />
+
+
+ ))}
+
+
+ {
+ onDeleteAction && onDeleteAction(item.id);
+ }}
+ />
+
+
+ ) : (
+
+ No recordings found
+
+ )}
+
+
+ {/* ========== CHILDREN ROW ========== */}
+ {expanded && (
+
+
+ Text-tracks
+
+
+ {status === 'idle' || status === 'pending' ? (
+ Fetching text-tracks....
+ ) : status === 'rejected' ? (
+
+ {error?.message ||
+ 'There was an error while fetching the text-tracks'}
+
+ ) : status === 'resolved' && stts?.length === 0 ? (
+
+ There are no text-tracks's for this recording
+
+ ) : (
+ <>
+ Found {stts.length} text tracks
+
+ {stts.map(item => (
+
+ ))}
+
+ >
+ )}
+
+
+ )}
+
+ );
+}
+
+const expanedStyles = StyleSheet.create({
+ expandedContainer: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 5,
+ color: $config.FONT_COLOR,
+ borderColor: $config.CARD_LAYER_3_COLOR,
+ backgroundColor: $config.CARD_LAYER_2_COLOR,
+ paddingHorizontal: 12,
+ paddingVertical: 15,
+ borderRadius: 5,
+ },
+ expandedHeaderText: {
+ fontSize: 15,
+ lineHeight: 32,
+ fontWeight: '500',
+ color: $config.FONT_COLOR,
+ },
+ expandedHeaderBody: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ },
+});
diff --git a/template/src/components/recordings/RecordingsDateTable.tsx b/template/src/components/recordings/RecordingsDateTable.tsx
index 6089cdb96..700f050bc 100644
--- a/template/src/components/recordings/RecordingsDateTable.tsx
+++ b/template/src/components/recordings/RecordingsDateTable.tsx
@@ -1,30 +1,67 @@
import React, {useState, useEffect} from 'react';
import {View, Text} from 'react-native';
-import {style} from './style';
-import {RTableHeader, RTableBody, RTableFooter} from './recording-table';
-import {useRecording} from '../../subComponents/recording/useRecording';
+import {
+ APIStatus,
+ FetchRecordingData,
+ useRecording,
+} from '../../subComponents/recording/useRecording';
import events from '../../rtm-events-api';
import {EventNames} from '../../rtm-events';
+import {style, TableBody, TableHeader} from '../common/data-table';
+import Loading from '../../subComponents/Loading';
+import ImageIcon from '../../atoms/ImageIcon';
+import RecordingItemRow from './RecordingItemRow';
+import GenericPopup from '../common/GenericPopup';
+import {downloadS3Link} from '../../utils/common';
+import {useControlPermissionMatrix} from '../controls/useControlPermissionMatrix';
+
+function EmptyRecordingState() {
+ return (
+
+
+
+
+
+
+ No recording found for this meeting
+
+
+
+ );
+}
+
+const defaultPageNumber = 1;
function RecordingsDateTable(props) {
- const [state, setState] = React.useState({
+ const [state, setState] = React.useState<{
+ status: APIStatus;
+ data: {
+ recordings: FetchRecordingData['recordings'];
+ pagination: FetchRecordingData['pagination'];
+ };
+ error: Error;
+ }>({
status: 'idle',
data: {
- pagination: {},
recordings: [],
+ pagination: {total: 0, limit: 10, page: defaultPageNumber},
},
error: null,
});
- const {
- status,
- data: {pagination, recordings},
- error,
- } = state;
+
+ const [currentPage, setCurrentPage] = useState(defaultPageNumber);
const {fetchRecordings} = useRecording();
+ const canAccessAllTextTracks =
+ useControlPermissionMatrix('viewAllTextTracks');
- const defaultPageNumber = 1;
- const [currentPage, setCurrentPage] = useState(defaultPageNumber);
+ // message for any download‐error popup
+ const [errorSnack, setErrorSnack] = React.useState();
const onRecordingDeleteCallback = () => {
setCurrentPage(defaultPageNumber);
@@ -38,7 +75,7 @@ function RecordingsDateTable(props) {
};
}, []);
- const getRecordings = pageNumber => {
+ const getRecordings = (pageNumber: number) => {
setState(prev => ({...prev, status: 'pending'}));
fetchRecordings(pageNumber).then(
response =>
@@ -47,8 +84,13 @@ function RecordingsDateTable(props) {
status: 'resolved',
data: {
recordings: response?.recordings || [],
- pagination: response?.pagination || {},
+ pagination: response?.pagination || {
+ total: 0,
+ limit: 10,
+ page: defaultPageNumber,
+ },
},
+ error: null,
})),
error => setState(prev => ({...prev, status: 'rejected', error})),
);
@@ -58,26 +100,58 @@ function RecordingsDateTable(props) {
getRecordings(currentPage);
}, [currentPage]);
- if (status === 'rejected') {
+ if (state.status === 'rejected') {
return (
- {error?.message}
+ {state.error?.message}
);
}
+ const onTextTrackDownload = (textTrackLink: string) => {
+ downloadS3Link(textTrackLink).catch((err: Error) => {
+ setErrorSnack(err.message || 'Download failed');
+ });
+ };
+
+ const headers = canAccessAllTextTracks
+ ? ['', 'Date/Time', 'Duration', 'Actions']
+ : ['Date/Time', 'Duration', 'Actions'];
+
return (
-
-
-
+ }
+ renderRow={item => (
+
+ )}
+ emptyComponent={}
/>
+ {/** ERROR POPUP **/}
+ {errorSnack && (
+ setErrorSnack(undefined)}
+ onConfirm={() => setErrorSnack(undefined)}
+ />
+ )}
);
}
diff --git a/template/src/components/recordings/TextTrackItemRow.tsx b/template/src/components/recordings/TextTrackItemRow.tsx
new file mode 100644
index 000000000..a028dcc09
--- /dev/null
+++ b/template/src/components/recordings/TextTrackItemRow.tsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import {View, Text, TouchableOpacity} from 'react-native';
+import IconButtonWithToolTip from '../../atoms/IconButton';
+import Tooltip from '../../atoms/Tooltip';
+import Clipboard from '../../subComponents/Clipboard';
+import Spacer from '../../atoms/Spacer';
+import PlatformWrapper from '../../utils/PlatformWrapper';
+import {FetchSTTTranscriptResponse} from '../text-tracks/useFetchSTTTranscript';
+import {style} from '../common/data-table';
+import ImageIcon from '../../atoms/ImageIcon';
+
+interface TextTrackItemRowProps {
+ item: FetchSTTTranscriptResponse['stts'][0];
+ onTextTrackDownload: (link: string) => void;
+}
+
+export default function TextTrackItemRow({
+ item,
+ onTextTrackDownload,
+}: TextTrackItemRowProps) {
+ const textTrackStatus = item.status;
+
+ return (
+
+ {!item.download_url ? (
+
+ {textTrackStatus === 'STOPPING' ||
+ textTrackStatus === 'STARTED' ||
+ (textTrackStatus === 'INPROGRESS' && !item?.download_url) ? (
+
+ {'The link will be generated once the meeting ends'}
+
+ ) : (
+ {'No text-tracks found'}
+ )}
+
+ ) : item?.download_url?.length > 0 ? (
+
+
+ {item?.download_url?.map((link: string, i: number) => (
+ = 1 ? {marginTop: 8} : {},
+ ]}>
+
+ {
+ onTextTrackDownload && onTextTrackDownload(link);
+ }}
+ />
+
+
+ {
+ Clipboard.setString(link);
+ }}
+ toolTipIcon={
+ <>
+
+
+ >
+ }
+ fontSize={12}
+ renderContent={() => {
+ return (
+
+ {(isHovered: boolean) => (
+ {
+ Clipboard.setString(link);
+ }}>
+
+
+ )}
+
+ );
+ }}
+ />
+
+
+ ))}
+
+
+ ) : (
+
+ No text-tracks found
+
+ )}
+
+ );
+}
diff --git a/template/src/components/text-tracks/TextTracksTable.tsx b/template/src/components/text-tracks/TextTracksTable.tsx
index afcc7f33a..fca0afdb7 100644
--- a/template/src/components/text-tracks/TextTracksTable.tsx
+++ b/template/src/components/text-tracks/TextTracksTable.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
import Tooltip from '../../atoms/Tooltip';
import Clipboard from '../../subComponents/Clipboard';
@@ -204,15 +204,18 @@ function ErrorTextTrackState({message}: {message: string}) {
}
function TextTracksTable() {
+ const {getSTTs, sttState, currentPage, setCurrentPage, deleteTranscript} =
+ useFetchSTTTranscript();
+
const {
status,
- stts,
- pagination,
+ data: {stts, pagination},
error: fetchTranscriptError,
- currentPage,
- setCurrentPage,
- deleteTranscript,
- } = useFetchSTTTranscript();
+ } = sttState;
+
+ useEffect(() => {
+ getSTTs(currentPage);
+ }, [currentPage, getSTTs]);
// id of text-tracj to delete
const [textTrackIdToDelete, setTextTrackIdToDelete] = React.useState<
diff --git a/template/src/components/text-tracks/useFetchSTTTranscript.tsx b/template/src/components/text-tracks/useFetchSTTTranscript.tsx
index 838f7e87f..edaf673b7 100644
--- a/template/src/components/text-tracks/useFetchSTTTranscript.tsx
+++ b/template/src/components/text-tracks/useFetchSTTTranscript.tsx
@@ -5,11 +5,7 @@ import getUniqueID from '../../utils/getUniqueID';
import {logger, LogSource} from '../../logger/AppBuilderLogger';
export interface FetchSTTTranscriptResponse {
- pagination: {
- limit: number;
- total: number;
- page: number;
- };
+ pagination: {limit: number; total: number; page: number};
stts: {
id: string;
download_url: string[];
@@ -23,118 +19,110 @@ export interface FetchSTTTranscriptResponse {
export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
-export function useFetchSTTTranscript(defaultLimit = 10) {
+export function useFetchSTTTranscript() {
const {
data: {roomId},
} = useRoomInfo();
const {store} = useContext(StorageContext);
+
const [currentPage, setCurrentPage] = useState(1);
- const [state, setState] = useState<{
+ const [sttState, setSttState] = useState<{
status: APIStatus;
data: {
stts: FetchSTTTranscriptResponse['stts'];
pagination: FetchSTTTranscriptResponse['pagination'];
};
- error: Error;
+ error: Error | null;
}>({
status: 'idle',
- data: {stts: [], pagination: {total: 0, limit: defaultLimit, page: 1}},
+ data: {stts: [], pagination: {total: 0, limit: 10, page: 1}},
error: null,
});
- const fetchStts = useCallback(
- async (page: number) => {
- const requestId = getUniqueID();
- const start = Date.now();
+ //–– by‐recording state ––
+ const [sttRecState, setSttRecState] = useState<{
+ status: APIStatus;
+ data: {stts: FetchSTTTranscriptResponse['stts']};
+ error: Error | null;
+ }>({
+ status: 'idle',
+ data: {
+ stts: [],
+ },
+ error: null,
+ });
- try {
- if (!roomId?.host) {
- const error = new Error('room id is empty');
- return Promise.reject(error);
- }
- const res = await fetch(
- `${$config.BACKEND_ENDPOINT}/v1/stt-transcript`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- authorization: store.token ? `Bearer ${store.token}` : '',
- 'X-Request-Id': requestId,
- 'X-Session-Id': logger.getSessionId(),
- },
- body: JSON.stringify({
- passphrase: roomId.host,
- limit: defaultLimit,
- page,
- }),
- },
- );
- const json = await res.json();
- const end = Date.now();
+ const getSTTs = useCallback(
+ (page: number) => {
+ setSttState(s => ({...s, status: 'pending', error: null}));
+ const reqId = getUniqueID();
+ const start = Date.now();
- if (!res.ok) {
- logger.error(
+ fetch(`${$config.BACKEND_ENDPOINT}/v1/stt-transcript`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ authorization: store.token ? `Bearer ${store.token}` : '',
+ 'X-Request-Id': reqId,
+ 'X-Session-Id': logger.getSessionId(),
+ },
+ body: JSON.stringify({
+ passphrase: roomId.host,
+ limit: 10,
+ page,
+ }),
+ })
+ .then(async res => {
+ const json = await res.json();
+ const end = Date.now();
+ if (!res.ok) {
+ logger.error(
+ LogSource.NetworkRest,
+ 'stt-transcript',
+ 'Fetch STT transcripts failed',
+ {
+ json,
+ start,
+ end,
+ latency: end - start,
+ requestId: reqId,
+ },
+ );
+ throw new Error(json?.error?.message || res.statusText);
+ }
+ logger.debug(
LogSource.NetworkRest,
'stt-transcript',
- 'Fetching STT transcripts failed',
+ 'Fetch STT transcripts succeeded',
{
json,
start,
end,
latency: end - start,
- requestId,
+ requestId: reqId,
},
);
- throw new Error(json?.error?.message || 'Unknown fetch error');
- }
-
- logger.debug(
- LogSource.NetworkRest,
- 'stt-transcript',
- 'Fetched STT transcripts',
- {
- json,
- start,
- end,
- latency: end - start,
- requestId,
- },
- );
- return json;
- } catch (err) {
- return Promise.reject(err);
- }
- },
- [roomId.host, store.token, defaultLimit],
- );
-
- const getSTTs = useCallback(
- (page: number) => {
- setState(s => ({...s, status: 'pending'}));
- fetchStts(page).then(
- data =>
- setState({
+ return json as FetchSTTTranscriptResponse;
+ })
+ .then(({stts = [], pagination = {total: 0, limit: 10, page}}) => {
+ setSttState({
status: 'resolved',
- data: {
- stts: data.stts || [],
- pagination: data.pagination || {
- total: 0,
- limit: defaultLimit,
- page: 1,
- },
- },
+ data: {stts, pagination},
error: null,
- }),
- err => setState(s => ({...s, status: 'rejected', error: err})),
- );
+ });
+ })
+ .catch(err => {
+ setSttState(s => ({...s, status: 'rejected', error: err}));
+ });
},
- [fetchStts, defaultLimit],
+ [roomId.host, store.token],
);
+ // Delete stts
const deleteTranscript = useCallback(
async (id: string) => {
- const requestId = getUniqueID();
+ const reqId = getUniqueID();
const start = Date.now();
const res = await fetch(
@@ -148,7 +136,7 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
headers: {
'Content-Type': 'application/json',
authorization: store.token ? `Bearer ${store.token}` : '',
- 'X-Request-Id': requestId,
+ 'X-Request-Id': reqId,
'X-Session-Id': logger.getSessionId(),
},
},
@@ -159,44 +147,33 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
logger.error(
LogSource.NetworkRest,
'stt-transcript',
- 'Deleting STT transcripts failed',
- {
- json: '',
- start,
- end,
- latency: end - start,
- requestId,
- },
+ 'Delete transcript failed',
+ {start, end, latency: end - start, requestId: reqId},
);
throw new Error(`Delete failed (${res.status})`);
}
logger.debug(
LogSource.NetworkRest,
'stt-transcript',
- 'Deleted STT transcripts',
- {
- json: '',
- start,
- end,
- latency: end - start,
- requestId,
- },
+ 'Delete transcript succeeded',
+ {start, end, latency: end - start, requestId: reqId},
);
- // optimistic update local state:
- setState(prev => {
+
+ // optimistic remove from paginated list
+ setSttState(prev => {
// remove the deleted item
const newStts = prev.data.stts.filter(item => item.id !== id);
// decrement total count
const newTotal = Math.max(prev.data.pagination.total - 1, 0);
- // if we just removed the *last* item on this page, go back a page
let newPage = prev.data.pagination.page;
if (prev.data.stts.length === 1 && newPage > 1) {
- newPage = newPage - 1;
+ newPage--;
}
return {
...prev,
data: {
stts: newStts,
+
pagination: {
...prev.data.pagination,
total: newTotal,
@@ -206,20 +183,80 @@ export function useFetchSTTTranscript(defaultLimit = 10) {
};
});
},
- [roomId.host, store?.token],
+ [roomId.host, store.token],
);
- useEffect(() => {
- getSTTs(currentPage);
- }, [currentPage, getSTTs]);
+ //–– fetch for a given recording ––
+ const getSTTsForRecording = useCallback(
+ (recordingId: string) => {
+ setSttRecState(r => ({...r, status: 'pending', error: null}));
+ const reqId = getUniqueID();
+ const start = Date.now();
+
+ fetch(`${$config.BACKEND_ENDPOINT}/v1/recording/stt-transcript`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ authorization: store.token ? `Bearer ${store.token}` : '',
+ 'X-Request-Id': reqId,
+ 'X-Session-Id': logger.getSessionId(),
+ },
+ body: JSON.stringify({
+ project_id: $config.PROJECT_ID,
+ recording_id: recordingId,
+ }),
+ })
+ .then(async res => {
+ const json = await res.json();
+ const end = Date.now();
+ console.log('supriua json', json);
+ if (!res.ok) {
+ logger.error(
+ LogSource.NetworkRest,
+ 'stt-transcript',
+ 'Fetch stt-by-recording failed',
+ {json, start, end, latency: end - start, requestId: reqId},
+ );
+ throw new Error(json?.error?.message || res.statusText);
+ }
+ logger.debug(
+ LogSource.NetworkRest,
+ 'stt-transcript',
+ 'Fetch stt-by-recording succeeded',
+ {json, start, end, latency: end - start, requestId: reqId},
+ );
+ if (json?.error) {
+ logger.debug(
+ LogSource.NetworkRest,
+ 'stt-transcript',
+ `No STT records found (code ${json.error.code}): ${json.error.message}`,
+ {start, end, latency: end - start, reqId},
+ );
+ return [];
+ } else {
+ return json as FetchSTTTranscriptResponse['stts'];
+ }
+ })
+ .then(stts =>
+ setSttRecState({status: 'resolved', data: {stts}, error: null}),
+ )
+ .catch(err =>
+ setSttRecState(r => ({...r, status: 'rejected', error: err})),
+ );
+ },
+ [store.token],
+ );
return {
- status: state.status as APIStatus,
- stts: state.data.stts,
- pagination: state.data.pagination,
- error: state.error,
+ // stt list
+ sttState,
+ getSTTs,
currentPage,
setCurrentPage,
+ // STT per recording
+ sttRecState,
+ getSTTsForRecording,
+ // delete
deleteTranscript,
};
}
diff --git a/template/src/pages/test.ts b/template/src/pages/test.ts
new file mode 100644
index 000000000..4ec966b88
--- /dev/null
+++ b/template/src/pages/test.ts
@@ -0,0 +1,4 @@
+export const AudioData = {
+ audioContent:
+ '//OAxAAAAAAAAAAAAFhpbmcAAAAPAAAATQAANv8ABQgLDQ0QEhYWGx4iJSUoLC8vMjU4ODo9QEJCRUhMTFNZXV1gY2ZpaWxvdHR3fICAhIeKjY2QkpaWmp2goKOorrS0uL7CwsXHysrNz9LV1djd4ODj5ujo6u3v8vL09/r6/f//AAAAPExBTUUzLjEwMARuAAAAAAAAAAAVCCQEACEAAcwAADb/UyrZbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/zoMQAErAGgb4QAAASdySX/b/7iMnBAEAwUcH1g/cQBCsHwfD90oCADD5d5OIAQBB0uH/8H/OeHyjlAgGcTn1HChysPxO7BDw/DFQDl3lzkMfwQd//+IELO/bb6/fbXiF1khKQgEHQRk29EbVJkSBMxpVW8ivCBrUXSmliGbiUen1MVIrlDn5SFPeWjRciHcvfZ5ZempHyFDJtZxFV1+P3Mn5lLlnDfnr3KO19C7mlzeZatImRnRleXszVe1jW/07boxv6q6o2h6WIDdVnPdiglhUtKgRIdv/v9tvra0MowiqKz6AUnEQpIRHJCucJDgGUaQspMmiBlSfJijjWR3Tpu6kJEQAwyRSRbatJYSqxbqR66ukIiemcjOZVHpR4veqWcvFY6oOs5iNw3jy9n56mMwTvblvmZvv/82DE4R8L+oJeSMU9G/+X/fMXX0r/DPvkKsb/bUf7s/+sWA/ztt/1VHUEV4SXbbXa62iKuaKtXLxEQEQrWklVKpBuFHHhPQEdyEB4JeiXuLdQjk4cTuIZKJwks+khXIPUsNi8jL+GRvJJF4xaPEM8TDY0TJyQEGRXY6XFSY066Gx4sGFmySg2rkhZFroqs5IigukwUFSFZRiBOyf/81DE8yAaDn7+SYaxSqzRgjgZzKUGve22y222Njsex37i+OxCX0B3HWrnCiYb4w4gF6MVESM0haAz4FQnixyG1i/bvxkWzpSKZ6vWdyfVQ3Ta7ViOD5GuhofLTbKdamdMoQMwXD4ta4B2AmTNvcCyIGCwWrc1l5EY5gjJC2u3XKu4//NgxOcecbZ6/mPGYKUTHc7X2pUFXWaSWWW2WhU7T+IXMkDDgqTpmGQaA8QDhllOaNxVNl4mbIZuwgIooEkG9hYcLCYiQjr+vTgeKZo6ff9pO8ub9ZPgtPqDVVyAaO/y+rZ+L58PRCOq5XnfzZt8/of3vn/y03/fSlWsu//9L1cut3lGv5XcbxVzWXNqDcdWIhtv/bJcFNtPE3aO//NQxPwc4fpyXnmGqFWVxtpjiV4KpILgtm7P1yF3F+tgKgTm0u+i2YBGtJ3W26dAhOQgSMByAoRA4CIBAi6patfVRxVjqBvMywuC7iWP850NLGdhwKBgdEoNBJHge5ezsE0H45H4GGUB0GgPsI+UQ9ybk4aG4eh4iy3kLQteIQstaP/zUMT9Hhmedl7CRhlavorAEseHiZRZO2mRQH4oEWq1AxqYgjirGSD7KQBAghc20rMmbu3Kk/UwQIgyPFtDkygAh4a7KIZGHoQ+rMV4RoacAe7Bn3R/+zGWiwAI+RtrtF462B8/yb+209UGFu//W722T3J7PGw/CpJW/k/b+AGl4wz/84DE+TgKxn5+w81Rs0b1mTW1D0nAEECDEvNcKQaUsW8giZQDihEMHiQhAgIia48aIWEPgWABgYlAmHALpHBiHJZD+uhEpTI20fd4aa4/tLfh1+l5oSGOw2pFYZUCliXy5FZmCwbXUsYtKl8qKS5ga5lmMkSedkRAUwmXsZT6RoSEeRq6rmehwEcAdKLA9FwGhQQ3iayrLB8frCkw1ZchJ0Ee6sryYfEhbejXfziNCYWUunQufWKH1yXkV1RrGcXb0f164x8wDi7s/zaONd/U//OQxNtALBaKXtMNyDa7M17pBDbjM9xHZv4z4/x9x88f7DbMBidp4+s8H7iN62blyeZSjtrpKApyS9GL79Tk+DkmUSwQiTTeckiqRFM6i6kC4NjB8FAZIBohUEEtL4ucCIfDxENoE8JmvhIWSRO+22UnuNCJfNa4tzX2G537ZpR8wWtIldJH4lokBpRqN2CEXVGe0nEKvpQnf4M9zfVwWf6g2E5ueT9KTHgFn0QYLGr93z9m/q/rfNDF0/t9HvswKkeXRcAZiul3ZHAjvtsbl5GuJJoZAZwPGDGpkhgZ8dCANi6yDAAASBUNh4IQwQkFrFhX6XIoAySSvqw9nciypHSVKEYN//NwxNIjcaKiPnpMlZG3ln9YTk2xJdddBfZSl7so6twkV7EydZybo+FtLySmRubQ8zpjijDuLeIxDFl+fYxcjSl021mpev7cn3/n3tc0SiMzDcQFnhopTohMDLMB2LCws1TAQ2UmXiryz7h6jYEqUNCRank3v3bzWTiqAEYl3E3zhRR4i+XEjF/Myl7WAELWrSxFmZEbZZt1DBhU4Zt2VKwMTS5dVjrE0NGPP9A6c77VqlWxbaP/83DE7SlSxqbe2kcQY+eNqhgOE0CwgJW5zD40SqBgcH6OkazsZKrYujldrrw9VBvtzyaxAgN9FDG5IFG4Sqt8XQqEUNDvX1QmjZenqy+pVyGOoOzMEIkgwZCBAMACqNaJorSNFz5CPOhR7FlpdlNB/ubVgFAgGSXpjS2/cNj2S60OpOYnzk0GhwrV2gCihIU+7y8DNJEamjhgg0Ghf5HQWwpdBEWjp6KwVlQwAMQoNQfStrjxgf/zcMTwKwq+pf7SRVA9TE+T1TkJXNkt3R4BsZGYrTQ/EcNWtZx61HXdoxXJ9/a9z7tftYqiY8pYgMc56/dGMlV//9tEprRGMhzvZRtg+EUOOiZSnGshSPVSEM7GNp5Gu1bs1n2HSDeu541h0WffOqJKQ0sm76/RPuR2kVynFGostksDI08bN5uYuORPwvsHTL0s7xLcD0qWQumUHir3wwvwzieV9Wfi3odbeEF1GvRd9GmB81HA//NwxOwpm3KiDsMLTE1SfGBZQjFeShBRHRAFqg5Xqkv+0y7rdo2ty6OHYchyikc62OUUSYqldn/Sdiorun/1Q/2bT082gZ5w6DOo8yA0Vw7rYHFpQtqSdmkfYx2DERznfbQlwiG774TqQFADLN4p2UFUaYlWckpVcpBH9nggDLZLKFLVRPo8IF2rCz2w+hb57ozQtwi0gia8mvR6dexB512VDgEtUzHRdB56cGgEEJC8kBpVYnD/83DE7igTWqFGwwVILAKmSgKKQgdFZIiVTk64MpT8K2Nx7OpTs9zHJCAmHDOMpgxXIxPM1nsv//sy/+1FIjIV5yHO7Zwx1VjGYj4AKNR9KOrtYPYISIEsSIZPuSXGi7fLrcFaQgC679VNKKk+JRt0AisLjDAxIU/GFgwWJiZBUSAmOzasbDM45Pdld1PlQzxUwooCiMCx+EmjBIhbx/LhLoeooTm2KGV4uEJqunJncGF03xdBAv/zcMT2KOM+oe7CRUjNwTYOfLtCY7FWmvVNdMTpBQjA7h0IQF//4+MGHQEGENtLiMiGSihilC6wdDj3DHOvv3TCXOaJByChQugyllUMb3zmKkYK4n0n0/mzWGmMJgCHE6BwddhAC0nyEpYoLOrW5RzMUFkpCa3Cl5FrFVwY0ren0YpiZKUl0Q0IJ+w1fIraK+xy9gYFdLGoHlY5UZVQsx3od6kZlIzVBj1FnxUjIyNq6qU9St////NgxPskCbqhTsPGuP///92QivPRnd3FGK7WMu37DyOUWYh20ZG/V779+26b/72dmGnC1Qf8m9yVuuzwnBKmfSrGGU94zEG1Lzv66KfKw1FlLjpbRCXMS3WD79aKX3VPPnpNbOBcB45Eovp2Dlzk7p5Qvnq+xSebthgYNWIcrVR6GMxHKezDqziXcZnOMxEHcQh5NwbIdUVL9P////NgxPkkFA6gDsvKuf/95qmM7MMaTimKxtVPb7TIt2KpTorf69P/qnG+kVBAuoCgBueXs5ZooaQBO+DZWiQUYSLMw5RFRJGQ91t3Tl6BfFFQ7lx5qtaT3tZevHzJ1cvno4C5eok9rBz/LfQ+TP1ikUUNVnMh71oe1ZEdimMCMHKxkKrxE1aNNLT///+nt/eqoeWMdEB2ERqjrjhx//NgxPciK86oFsME1LUJxzwgpltNFXaHw24Obmn2g8opIbkAWu/38SCKxqA3RieFqorQ0ObjqYSUvI6zqLPNPaexT0SOisZkmQ/xvOSeYOTIq1n+F4S3X7e8IySmEC3kpjcztuT6fO8+Uti4zY1FK2mZ+VMMQ3Kn/////ozaGdVQxWIXY5b1uhCuzd3BiaCjug7oUgzir1DrXo5g//NQxP0gesak9nsEuNqJT/UREioOIANz+aufHFM4mBRpcLaUlLF6wa+u67qvq6s66+FBKZAKOUbjEkrbdss9fxtSFFEgLg4PGiyqKjJo0o21mznHSfN3MVFvM/0nd7cWbOYUs7y01F/MwroLB6KCDTKvfcS0V2vHO/ds9f9dzH1HEf/zYMTwIEtaqR55hPwXXRNUtQsaRbcMvaz10NqRQeQUFIlcgtvHYtRT/fr/fCmqvb2wu1UAxXEdL/JVabjwNHMCVI2pVFp2bls7S0EqYlLhJGKokSSqfVVW/1XJV+dZ1AwXhxIlva5/NRNuSMJf81J0Z1Eo4uqpjVSVmPpUqykFEk2yllsf+RSLDz11gZ4NB0DNSSLKmlFRiAmJcv/zYMT9JTs2kH7BkK2ikeHYKhMqdedBWkktfJNI9R5pW1sb3cBNAUqv/12++twkbJr1hFWjieLNMiVDNtCh4wK0PNEqhlhxlhFqG50QqqsiCUSwRgwoSR5+H/OhQ4ymGclWJG0QtdIStCBzXWiaIgaz72s/YkqNgtVjQSevYN/pOK2HJ6iXNbk5//j5ax36f9/7gb/4/78e6/9r6//zUMT3IEIahZ7BhrxXzdzR6dc7e9IC2W3/fbba3Di2EpSKqu520er3H8ZZbbXPuWxhzrjVPYHxg+6ORzDCFGwMlpkoXG6VdvaI0UNGInwhES8XIyVlKKpfpTI11rq5uu5h7+I0m5KrqdMddbR9EsVCT7wRZ1uyLiIP06/Vd97/Smr/82DE6x+pinpewkY9NO+b9//I3fvIfe+ofGtcgA3c14JCzNsacUcGlVwHCy9sq9ck5XAB0aoaSqGChpl5iBUFD280wx8FMYCjPl4wQMIhtDU0owQSqxGBm6GCXDLC+hmFhshxcox55roQXaWn2HLzEIjTBlBCUIYRm0izUlprzpjQJetPE1Og1JMyAkRjM79i5NGJJgJOHHBCJCD/81DE+yDiLnZfTEABuxJz4GVWQ5Uf52rFewWwMAAQoRzpn4cdO8WFxpHCONEWrYuZ/he1YpZdFEA5ctNdmj7u+/Du1V1wXL56NRqxbu2Lf55frDdJ2WY4zFjtJYzqXqetBUBSuXz1i9jzPsrr//9/9/v+c53//vf5nhhnfsUnO65d//OQxOxAIxqK/5vRIMLGq9eHY9arxCDMbVWzZ0+iH29xuiBBAJM22HBGtnjmQMEzKmpOEwtvM7gTTwkwk8MrETFAYehgojmBJiJ5kYQFgshCTFmE/klPCIAEKGGlxnBCaGOGKOwCNSmMMBmDGb8HE4JNTAiw0QDM7GxgYN0mTTRox0PKC4OiDDwBH9I400TNIGgUEjwINDJgwUFgxB4OBGPMbeJnLNCgPRAV8gqXPAxaUEapyEDVUWymMjrATtP8/0fmoiPBS5KjcZW9y4QaADgEDQUYCn7a2kg2kTgV9ZC1psUiluLgSdlrW3EbZxJuTNCa28kUhph8BymhlTlQh5GsxJ3Z//OwxONSS/qFT5vZAWO1dqUsql13OleeUZbv9y5jhX3lQTdun3JMGvuLOs4jr4vPCs8t7ys1q8at4zMMzHz2eGv1hSW+XZ2xPxCmnKe9e/X/3uf8/L/5//znP53HHP8v/f7//y/9ymmnMaez3Wtd525fDdVAECAECAMDxdLyMAMQ+f85Pdax5qiewceIJQY6JZiAAhOAICAUIWWGWFEtImGwKFkIEHKxqyiYiXmWCBiMkYIHiwqzxoRecwFSAooJLRh4CYIJlUCBgSio3M0YPBLaY4GkxMDlcgcywAGYAD/tSYBDTKWkgIDBQWMiAyACIGEgIGBhYFEwUv1+uVL3vaW7gGIUjV6sym2hw+GEyfrNFZ3pdZ2ZZDk3hlL6ftFATfL5kqkVHppp0ll7NlyNefFsjNXUeNyJZR2qvbu92NUl+Wy2hrxWtlh+U1j9HCtWqe53+5bqbxv7wlNiXxq9Jn9vZX7NvX8xwo6ladn/86DE+U+EBqX/mtglE+9aSyymmN6/dBelkG1a0A0cLgDV7mFqg1jZw5+e+f+tf//MWb+W5FH7N7nea/msMs8M7c7IIbh25DNunpMscql6f8enSJJpL8MpVDwPOtWkdpgRFizqVI/L9rUDIx+MhsZCMqXMtvXAuYHExFQ/owHRNRZhJIkVE2iFB3GI5oSAuBIoRhAwRZvMjMW04cPiUy6kXhujmGxqajpNk1mZWWuYmy1ufXrPrW5XRdI4bo1kegmmYFVSqD9D+xg7Or1f////qbn1tTa7GaMwM1OmgYpPRd1rZLQPrWtB7qTWm6k0e+667fZNZgmZzNJJaapnagyZ+tSJu6rBFhmUAn9tnisXPGg38bC8yYuGqpAXf4ywIKmhSjlvXwyZyYyGujYcGcY9p48FK/4R8091//OAxOcsjBapZ9iAAMk4fy4O+a+csF60RbjeDtTXoE3VYx6CaKqCDtUiqgg6IogJzBQqZzi13t3a6Dk38vT///+yV2V5SqjMowSDodETDnTDOybgOqRNaTwX/f/53Vtbnd6teDUjfHAUeoEBI6cbC915Qc51i3cKqYdDfxwERFrK0QKiU8ivZKqNXwypVndyzWzF6T5heFm/Wet5eVZ8iIlszXEZ487DtDpYS3lWoVu1Zm8tWX2KZ008aVaEU9GwhlaCV/YO7pQyJlUYCu1NA//zYMT3JCLuulbDyp1BS0CKlYRbjesUO4fFZSjq03Fjn7//9+vpyji7pZgVTCwsisUVW5kOWO4kxyqV7ELUap4w20qN6SSjCm2OYMHVw4QGeWronn8mzYheCHJ0Rwl0ggRnEgAJLTcomlqCiv50dxj664QMbwEieWdIeUMeGugxSwMyJOlItHuSEOmFvJzTRPMnd3YS/O9XevdYkP/zcMT1KVtmnYbKC4S2gefVNEHrCIY8SDwtlDqUMbUrat1bUv////p2a5bujlS5VSPKSaqOJKAg6a8kSDUOnVwWqfq13DSwav8SC6oAAbNAAVz7btvYmUOxYkAcCQ9B4UDzUR2GaSBlklAEmk7aKoqAIQwM1msuq1p1k7lvyp9oebVU8zcTpaeLpEvul2a1sWseoHTt2UBh7Xb/9VGv1B1SjD9nEGJi0vlR20kx1F38LO11NIdH//NgxPgjSuadbsvK1Lv6r/C3N78f9TX/H09bx3mp4ya5KnmSSoBekyKAySPFg7HhWk0o4vp2i7DxpTLkLIDFVQAFfiUJ/fvvH7dVn521j4NqbEQReaF41muKxQdq+m68tayI4WmDYRh1XMD9caJN+DEfafcfyd/SvYUB6HU61e7qopM+fudjXs6qvt/NuLqtN639d9W/bXy0QjI4//NgxPknCtaSXtsRCCCd//rvP+lz5OxC+ZEaWLWSOb6qyqPjomFAwUKBnpA44VS7I3VLjGAgpqjh65tCABgzmbSe+324QWYSpCy1YZRSsGZmsVMN2IRx0Te1pO4lF1FKjJp9ZFdh9yy88191OUE16hKlGIU+X/3LIxOSIIEBJpHCBIgtGWJA8OvQiMNtqOXEYJodFakf1JxnqqNt//NgxOsiqtqaXsrG3KSQTukLcHEc5KSUQZObsvZLze5pIkXY8SYUKETKpsQRRkZOQWkgVPvgSNEjCNESTxe13AmiZpZhTmFLnBG9d97D+e/VHLo/ak/mpo/NtAzGCC4Ci8RqMChMuJ7ZPl0QYYWAIHHLkZPCExQ6Ic31srv2+t8cviaA26IfzE/HUSiquHgAhmNB/qGnUnCyzRGa//OAxO8ytBaa/jYSCKKGyouYNTBas0wK9CqcRukp3rNORTKxhYUiYijVlY0QHGyoJJHPzYmDkuRFsrFlIPcUpMDhm5PbweHoSfAYH8iQnXnyjjI0wGbRY9nZOQDhEXHSdgGCMJCEUm8E+lTQ6wK4kumjbkAjebMmg2EBpjguPnjU0LQDiRQUaPhRyiAjJxQSGYiAqOlgMEhAJF+qT2QRQGnCcRifVbIxdAc6CJhKZEPEKbUFI2JESCSA3xMAQmiDYeKvh46BxO64mtpmEgNx9P/zkMTnPAwWpl5iWdCIIB6TyMSCq9FVgdczjU1yoEHZIEE4b7bYdG5gjQNyao1PVGq/l/PCkR4eiihccPCUoWckxbNuatiu4tammRlm5ipol223iLGWvNXcRzKW1NTTSmlxohMmhUGhoDcBkwbcKnVGorPHTJ4GhwNLvUdxzQqIolg1Q9NjNAsp6aw62sYeiKjSAAAgAAYFITCRcUscSKL7IGmlBZwufHjwkoFAcHg7rNBATAEQ5wnQgDgEMIDJR0NADc8RfZflFYCDJkpeHBZoEcZbfGaBQGDoflhnYyFiIyQMOBSiYSB2aztHh/lbC0wsGIHq0GEkIoIriR3pn6lSq0Rh9f/zUMTuHrmmqlVPQAC5AiczX004GGQNN9JJtoCfm/hB9ROtncoi8ceSuYUEMfm1NH+m24Tb8S10ItN0du25TAIAd6R01C4/HeaKsERADOGdwJHHb5PyqNQzKot3KkiUpkjv7f+YqX6ejq5WqT6F3n/hWMskdXVrdHM6yjUp7IbFLVv/86DE6E1kFnZfm9gANJlSXK09nTT2HKWpSSC07m78ipp21T9v35fjqJ4RG3TUVq3ljcuU3eY8xuVv5ljeyy3W1r//Pf/v//X///vko5e1j+Gtdw5n/K/edr16wAATmt3SDrM+XcexuZqjAgC7MtsQ+Jafp1lOjQYFJbjKAogs2lC01VMRsGiOkigNEA6EQaG2k4ASIsQ7yfEJyqT5KDKEVLZkXCyaIygYlssEwTpOF0g5JEVLpNk2eLZkgXzQ1RM1GxYLjGhgaIlA3LZkQ0lDM0JY2ROmbGxmTaR80NETQ3aozRZFVVI6tNEyZVXR3U9dHtb69mRWnZ01pmiC3LySNGo9NnaaJpoGbdgrLFhEM4CUW2E+93u/X/6HrZy/f/ONwBpgUgArLuqGs+hKGXCmZZKTLIAIRALT//OAxN4x4vadX9iQASYiQUdkY4IiVx7VUwGE2zBokSNrtbhPp1gaVnC1R0kz0kSmIxgECVnIm2bw7JKZwJdpoYJUFtgWQi8BmwXIABntPFzS6KlFmNliCaPbaUiwriBNG50prV22cWHFMsjusu+Qhf/nMTVP/89qydTKaSV3Rz0cEJJKZWKIdqUQ7XBFVWEC6y4o+XY912sqtDthty0oIGIAL6b1r1p/RdkgzqAZoXClJSJzWOdLAI12QEo7UFtWiJdKLxe8ibZp+Jax2AYedP/zgMTZKgM+nfbKRUyLouRAMrRQdyBbTX80UCdctAgaKtqC6ZpIIKCVUpB3OT3E711/HyWOYHEQzBKMEYo4dLnNJL/MRBBBM83shze3N/ZDvTZWd2ft9Mg2Ru+p01LdD3VdEsiKzdv/r2FvE3Z6qiBSEB0t8G6rqyFpal+YASg4TX5AlLO1tpvS2vgskOA+UOx1Ys/jk3KR1dXHumbcka/af7CfVZBEQh1f5dQhAQMKiANCRhsLEabR0CDi8yU2bgtd+Ld4qvOVPYhspSxNRKP/82DE9CSTvqn2wkVIPHChzFZ0VL7/U55TXT09v//9PbRnxmyOiFApByhMcdrtV1OpLzbJu7JTX+9v1RZ1eRR+/vlvrCDVYEQoAX13Z99SChLX2I6QTIoQiONJSsw1KkzjXfSrk1I2LJ53t8f0t8sLJ8wWVV39zwWWslJDTLvGjqimXr1z75me0YnzW775mccWtLutZL6+DtKQ5Sn/82DE8CZL1qoOwktNByGscrMOQNGA5WFu+uIHKjsv////p6Zb0pTUhnIa6ixBnY012LZ6XKzFNVdWWevReqd+5Nj9Gu1tGZWUZ4APZKofQzkDnweHFrhiIs6eQj9UDCw4lWOKdoBKf2J+xPrbYWabbEc09sMUTNYE5/3SAR44U60D6Fic9aTytfva76zAQ6Kx6ywvdQDY+ObM4dn/83DE5STEFq4Ww8S8/JGZ/7aajOeaqiITIaaWRzyy5IWLmrSn9Qd4doWWFEllB2wCs33xE8vIg00St7eSlRl1uuNVQAGsabNk/36poxBrcDGYYMDJfHgCCl134nKiHRM6FShlatU9P6GBldtGD6GEZhrfIo+faCDSzbh83cl2Y3ECX16Zj9Kuh6VEo7Cbki/1PJ6ejFOCajn3R0R87vKhrOV7EnVe7WWi5/mbREY53Si3IjsOiv/zUMT7IdGmkUbLzJxTEQx1d35na9nSzspjtZmciXpafVfISXnmgba23y8iBhQKF1tn9yIxR/zA4crBrj0jAaHCUNvzONgjUAzEtTgfF0qVlcjsYXK8n1RTdruGrdSklkopa03XgB5JCr9dkldeRzkOfvtPlj+de5ukqVJYsIyyLv3/82DE6CRr3pJe2kTV0k5WiDev3BkGMsqOA9cFu+6VJPV7UQp7FXCZpaWQyJ3oxFHLeqRRirF7V3GtU1gDhOZGNn+nOGfmW978kOytCRCgXrnwI8CE9PFyoihBEkRycdic0jYWKCQ7Fk2QF20Bs8qyWT4ro2SFheLZGojjy88LrpL0KBGZmdJx8niKAoub6HpCs8fqUCtkibTN1+v/84DE5TcUFo0e2NPo8ty1FKElhO51VogBhchNmSJCxK3WgmHLMtgsDa8klCELk60WtaVJDRvTKMLiOgMCRGSK5FQ321Vsu62Vx3DOkBK0iMmbsgvVjLakSBM2PtLyacPpLB8n4HJGHVeWwuSxPFkS2EdbTy86XC/Lz+Zfn+RWx7w86poXNaHhVU8zJ3YPCW1y00k1pUkMTJcy/hl2+dX/b/+kSg5DWmEAIt76bT/aaZJYX7ftQ1u5qqrPsXF7QXnQzQ+CYnPyAIQMSIBIrC5z//OAxMsn9BKq/sJG3LnnuZcUO7QVJZIeZUTY95kYV45l1GESNjZpNMRVTyvoFpJMjjXAn0QGlosokc3On5moZvTJoR7N0u/295l/wy+J9/zKWZub7mL04gbiZ94ebRpcv8oMTw0eVi9qTsCkUV8YilAIMKAJ3+arif5yJgyCULsxAEEOsgVIEFTnRkVupJQnIsMydh5atXzRZ100x2pK9XIg4+q6GYJiN68Mgf9nbBohE7RxpjZRDjnUQ8IjYcWP1zWIXBBbHfeya02C9XQCKf/zYMTuI2Nmql7KRrwxhQeG4NixZUaee5Hvaaw1VJO07xCElsBY8aCpIqIhWHSz2AIQsA8FSyHfTV+qegG59f6kATiwAclx1nzQQd7m7zayZC+zNJAkAMjd70Ul4pUN0Ue2HvCwC4BGgDwPoMwI8EpC5hzh1EyThFki+XTVCXjdE+kXkjpmdUbsxUMT7n0VSgibmrlwyY4tIuTSXf/zYMTvI7Gqpr7CRyA3MS+UTxxzJNz7n5dcxczrQm9NF6q6anQdD/dbev///30UqmRrotZaS3UkmkpJq1su1d3TnGLhkMUSP32xD2dyagAMQOZNpZQkASQ3dtMBPwEuBAwYA3jAKagRGaDwECzkjgxASEhkyAmMEITJFczsVNUWjRAI1gHMXEjHl80QhMCDTdRgzwNM/JzQhcyZXf/zcMTvKBM2pB9YgACmmTQpmwUYWGGKtZEfBwosVW8MGk0EhWCtybiJAqk1BIYn2ivIyFaLEZC3FFVVBw3Abk11U64EnaaHY+pc7CVLitGaS4sEMpeODKr+yKMQh1kkoGZPE4PgeIxtdq0ojKHClsajVxrdSLSaH6GavSyNurDcOy3f55ZdyvprRN2oer6ypble9jVfe/PY9/WXNfrn6yq6h2M9huxIrc9L61nX63exiEZyjVh9//OQxPdH64Z8V5vYAK7TX+Z/+duJTkxL8KCzL85V//z////////////usPv51Ptepbwr4u9dAAgAyYbNY3ltaQ6dGktxiIyb4WBQzNxQDaio0UpBxY+pqpqZWWGbkpi5aZsbmxKoUVTOow9S41BY0ikGlTAljEsjbvjPjTfHDoozUi08wS+NKEQyO4gFoYsBBwBdjXGnqwWneiagLXHuZ099NSJPSpIktSpkY0UtKG3EXSp+BVTz7vWZLGnAeVurXYah5rUddmUT8amYxGUmUDVOIhEXLj7XloKUl2XSZ0ypyXJdmNP89r+yqV15yftMwkEP55Y4d7c1jk7b+xb8fuSO3S49//OgxM9FozJ9f5vQAM3Mi+X//cN6/DWsf5hjWlc5hN0dDLLFjmPc69NNz1NTSne7GXP/LDGZnKCnvRmVTdexa7nb1UxPjjFpQfLD3qqM7wuOQlHiVQAEgKtbdIyCY0mGmORlY8KHJUbTTy44lFAVgPdY8vG7qRgwIZjDm0sZkoKaUpGauhtOmdIuCSCZ+DGnCgsHGWnRiRSaYXmRPRrTsZoXDQYDVsSfRQGMmBDjS0GBLAEAEsRQLQRBm0pDi0w4RJQADCL2Q+AAZYR+GnK6ZCvyHmQA4bEg5dLySRrll/JNXYhLKd3o2+0qeBZMCOlbcqC2TNMfxgCgjaUzDHkd9YJXUTuRLk7TTKh6wD2taeOJQ7DFLW1bqVM71THD8NNZhGeOGGrX53N3kxnZkd3leX3KHO1ctZ9y1v/zoMTkSZPWdFeb2ACwsRO/Zr7o86G/zlXL6W7I79Le5X1Ww/Dn/Z1f7jP0PbNavz963/4c/n//9/n/+v1/9//3///////6xw5cN3JQ0cSVAAYSrV21mTZlERNhM2UmgxIZRkcsyCAJnx4cSCF5rixvToOrmESnE/iNaZNiBCZmTIkiNyiMUPBJs7TIzgzEzpc65czoE0Jg0SA1Y0zRg2zI1hIvyDgbrF21rERBQ9AUBlZeQt4JB1LpEwOnpDCAUyZqI4Kxv+7cLpG/jE5Uk0zAlBNzEthrcRh+tGJfMP5L4jF3IpL8UwtWHSmHMj1/dBL+xh9Gvw/27T28X9mZRd39XPPeNb2kOvblmda7h/Zycn3bh+BOd5Vu7z139c7r7WpLX+3lenq1z+a3u9jaxz7Kb9+d1nrePN3/85DE6UJzHoBXmtABvkYkF+YltPcpN6Wc7/v/vSkIkMASPihM9YcHK+fqt5CZArRstcTRiYMZUkAgLMxNBEEApbMKLBAPFUbMAFwhwMaGTAxwwOGNYITfWE0EZN0KTRNVYjOiDMhBpoEFjkrRGXL/qmMauFUZi0giXmhgAQYCipkHI4FYbOCIGcFEYYyMDAqmMsKNcQFhirAIFTpl9uKgwCms4UrvI6KCMTjs/DPy2UzkXibq0ET5Ia0FPxAs7lTwzGpM/8Fyh2bsqlPKGMQA+8MSSvLLE3XbpROrFJZIJeyqxZlWOOq+d7eV2D34g63vnP1rDD33stPhveGqvctVq2Vq9nX/86DE10cjrnwXm9ABsZ/c5a3IJfZpec//3r9/YxyvUtNzust5dyrd7ELk5UlFJqrn+O/5v8Py5/ed1vt2gm90mfeayx1/anwYi4WHTt0oAA5VtSzvu+ou2rLWkFoUvF0tZXU0xBMXgSqEAE6gWZEmPMXmp54sZJ2NHSiPcZJWaiCjxBaSEClDEApQdATUJ+JaJeMGEWGWE2DkE8lCa5s5upIwdk0WXRWybJJqUl3stl12ugjWmy0zZlIPM0EqV1OqitBNV///9Svtqf/27rZdGi6lLZqK52ldXpJOgkktJ0HapaF3rV3Srspls7JNQSZe7oUnTtU9z6UVub7bqS8AqZO4PSWc36Gam6GRfR/QD0uAIxSONq5RUjWdZlce9gsrJPCiQW5jXS2W1iSB/GPAHGdUpBTTeMCP//NwxOYpdA6cF9hoAGSDJiikQxS6Y3z2DimzEe76jNOedzAzxwQkWcIcWwoEJZhqHYQ6BGEo6jDnNX9f//t/6HMtyystlRSWa6zs69lNftgjPYxzYFOSCW9+wg0YTcxApe35/Jq71GDF9JK6hqsT3lxDEkmMg0XqUqSoBS0by/zrtQiNNCeuPx0Q8XJWG2d7LvPQrVsa1azC8gkI2VCWEw0FZgqntxAJKxOhNOLOrOj1ISv1XKr/83DE6SZ7Vpgew8S5e5Xgs5mcEENR8Rt3VShFShihi6GEFrGWJ1rs8CVX3ANaI8ektHS9Pc517Ozc6trq/TX9QEZvtqy0KF0mwCxFPO6MCHBh0kJ7T3diQnue4hXR40vTlVVt+3UcyU8l5MTZrkEsaDr2ietO2mlknGbghTqwimjKVZ1ZzFTRjORB6hKPQdOcw88RdjEKUm3/+ttden+tEoqq1upCIi1EwQekIQPfrf/7XjEb9//zUMT4IBnqkDbDBrgD2B8E8n4gVV7lP+ZnvG1CG99um1Psbka3F1igFju+oKSLjbQIFmVoPB9YTlzU9Kb0ZexZBM5vpny75abJg5BNjIdkKommZa2BBwtGlIbhj25q/JMEngnEAwr4OlZmSRwlBcX/+GBUIH/i6TN6AAARVJEyhCH/82DE7CFC4phewkqdxAUTjWPBsmXQhAxK8uVFHCg11AqsKpB1ACjaILT3afXQ28ZaclGs1dClyOhcMM4XAVokAaMqgzJYnVSguxHvXls++EGUHzKzZ1uYcIRVNEIYYm6TDSaLWi2hGJwt6T7LI5Hqs6hiCNgGAqjaKfUOTcnYj//////5lUBqTkX8//Pn/lwuER7BTIHuJNGLann/81DE9h+x2ph2wYacWFC20Wy5ehankT8KF9TNrCdHo9Pv5WUsmXBGu5YoOgHov4nAlwZrrnHTs4FBMnLmEcXIY5GIrCVEEktm13QggZqOoMII52JsqLGDQgFSkzMGiJFCSiJSxdz2mqHkKqusqONnq1lednnKdxAGQPldVMhlEjA1//NgxOwj89qZtsJGneQyf///Zd1MuzZeu6r06UlKyOS1m/73ohmW9aI7qqoqq2Y093RyOQWcekP153amVIUDfeWwOOkQMcItAFyMUEaG1XiiE94jE8KNUJalm5suiMnLtI2kZCgxA0QNwN8GFmA6AhOceCplCTx8XjKM4wpDFpSpaucNXWqZlPUl2LU11zQdGzWttsQZB3IGTaNv//NgxOsjA8qQNsJKufeTG+SsPaDxAqGwCpSA0nGuWNQUDQqfVTezHlktLWMMrUDYSUoJv3KzVKQy1UrwEGVoJuHiO9wiVib8grl0TEnRXTfXpPPtOYQPdgzGUrDchb4/nCMS9+PlCxyoxDThZ0S4Pmalo9ZNUpjNrUbo8aJfO6zZrre9Uvi1MfHtumP63g7i01n2j2t/vFt115bZ//NgxO4g2eaQVsJGzMV3iSuN6k0b9vbhZO5/n1c58iTwL35BxOb1nlXN9+37TGUzHG7HtHfZ7r9dvIZHGa297kl+1SABACAimXS5bWkATEjYztnMLnjIGIy9eMJHDOoQy9UMGXTRjo0d+MFKiItNhKxGtGLjxiKYYODGYGlUqFLIt4lYdbpnnmm4RGAIdXCZRfwSZL2pPpDiXaAB//NgxPknyfaEF1l4AbqxESTZs8EPoeIJUfYRAziRhudNH4o59NRN6/LhvSzFf8QqyGtLNvG7bzXYLt0CXzoxOGKVlsrm9Q/IKWQY4dh+Ru68MijFizDEAuLG3uadLoG1KHUdOW37ljCpf3arWm5OlW3Yzuf/fr0sIiUC4/rKd5VsV97/XOZ4xiZu28JTSb3/f1jz+73ly9bv87+8//OQxOhC03ptH5vIAf9flh2lpbn/zvP33n8///ff///L/7Wc732v2+9SNzMlpejaFY9L9JXTf4GJpChW8oISFB8rGxkCNMSSaaUg87EbZDUeqX0b3yiVVKYKCCHpYLWCEo4OTVUkmlMiJEJxiNbUcSP2aYFQ5MRHq7LR0LVyYGfe3GWNexaGmleuuah+6Zc1IMCCaa1Fix0EM2ogKVpY6S2Le4qSSg6Ual9BpuwDCHVp1ihcEGNnXDlPWhUCAsmv/szPHiQTLmMbePmwTdhO8sRxWa0JIEgo4laZZsKNqtIlwbIxXG0mmft5O4ThWG3GHa+Hb7lHiWInnarDNT1rRv+rpphB//NwxNQikeKEH9lAABooE3gcgFRGsyEAsGoRB8kJyxoKvxgJVtKCApEZV4uHn7FVPW0IqHDTvuq39VUYWqkjwvQs4YXX2Apddv/zAOYWytJBETGyZNQlC2B+KaOVMKKekOktLpZF0kCOOZq8xWqiyvm4NJVVpJmaI7UxgRKQkmGp4JEqFq5sFTQjVEenYLhG2x8KRLDud/0788uwu+fM7lCQtrgyaW5P5UozW7HizWcFUpFJ8iL/81DE8h/hgoV+y8woIvMPCCWlpd0oZNEgeXpdG0IgxSi5rf//w0hX4PQy42UaoaS7ag0e/k/4VS/35iy0jcO1ISozVWrWVUKbwOiKFyU1GQutDztOZ/WLSIVLJ1utd0JscVcGiZMItQGjiWJUGUC+4pchURqUOIsx7GFoSNCiiQh5//NgxOcgqvaFnsJGXEodpOFbnndSkL9Lb11K+ohXbf//kZUDJtqW3kyukbnPD1spPDaFMxWNmG2C6PPZadO2ObSqgz5EVrmPmcXq3bbyotv4uejOlpbF5/tXkRq7Ap8qZ01u9q5kplkSWngzUottu3GuMccJM2oawkeEZSBnHg4RNJP3yyzD6Ezoi2lyxYi1dbkvkb1vXWpgtgXJ//NQxPMcKbaKPmJGlDfbcWFZwNkipG2U0dsKtr22e+ItRJi5FgOBbi1A1NVYGJEgsiw0aZVMjio/0vMrs6Rqz0klLyzl6aPDFUqYrJCqy+bHCTPUNdVenSy51v/+8/cpqJ21nZSkH8vVr8yvr/UwuK/3YXcd7jPEu0m0aSzYX/Owiv/zUMT3H1pChb55htwXNuJJzW//7j+YIaiU6sJqaxYS7j1hkXbZi9lAvG2m3ItjL1BGgYTUWGvu0v+aEM6B8qGFKMTascYzftX0mpRZxQm8sdi1/gnBM4CB5cLGHDBKUSE2EGMFZGKlg4kcRCEbLGAgtgEPMc9gu1iNFXoY3Yy1r6j/81DE7h4iAn2eYkYZgiIrkpoFGqRF227b/gXjQY1aEVD5NdWfHLxc2pdqpLC9dR99ZSD0ayj7j1znI2j89dYYfxO8tnWrV6AEOQyMjR1pOsrMmC2ftc+kaEtJicxGjg8mKl6TUysdXbLqSzMTsezLonbZU2SjutxLMhfTOWpiNabJ//NQxOoeYa6GXnpGXFK7dN1d6fbKteOk1lnWR7TsDEBSuu5J63b/cJHwjPzpQZIlNFp4bHh4KPLckUhFr5D0/ss2ia59F80w2vEsRDMGagZ3CkdVMUZFVSnkFjGkeV+IrQM6mCePSs3XzPP4Rp37ZMl/OHaREZeWR3an9Lu2Z0QR/f/zYMTlIPOWfl5gxWyD9oQjuH7KeKXd+jva9lsi//xiV0kCZ35J2OM667cjap45Lttx0W8Zi9W9GhAaEk5LgkkuDyJLVlQ1qjXO0x0PM7YY0pkFNWJmJvhmQacv0sjInYyk8WtCOfUWlpp6KY09j5v39Dk7GnPI3OvJb7f3h3bG8/d1Xs67q9WvzK+dy/Gvf7sKnPNYsbKBwOfolv/zUMTwH7rOfZ5hhpVgT7z2kTq6VUktttChuzGTyHJ0ITEIkHAtXdMMbNI4Fx88T+NIHOtl11ImJFJLQK8t2yda8RwCTIoIM7peK1Pt7fcMycgr54yNPXjJ/NzM1feHLAQ2d6grWJu28qr88/izXCnwrNWN9JrCwMP3+v5edh2/cJ3/81DE5h1xSnmeYYZ9W/xugZv/RYHN/u/lPd79eMUWeMguSXa7AMiuQidVsywhrU3WSrjCjUhu7QjFcHlswZJNdkDL8Kest2mfs4jWjU8P+pFoa5bWvzl9vDQ0s++cvH7Xz5v/f7N3NfMZ9/nNr94ZmR2YU+a7+Kbc8POF+Kb9j82P//NgxOUgIapxnnsMGfZ85TYhk/5nbGfN7tOR8f/36+Rf7x9vW71Xv2e3d/uf4zZDd93Wzdd6aKUg9S4fxRwsHhWFBIIxGIxAAAbiadRm0BDf+CA3WVa4P+mglFEJF/j4ABMAy6y7xzhIRgTE0L3E5ChLBImRuSJj4XAWxKCdiMoJKWj8eQ7C+WEmVFNdJVFf8dCscpLEsJkXCTMq//NgxPMk4+Zxv08wAUqiv/8zRTNEWTmykTx//+katJwR3f/+42QMpYNTWkxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//NQxO4fKfJaXZloAKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv/zEMTmAAADSAHAAACqqqqqqqqqqqqqqqqq',
+};
diff --git a/template/src/subComponents/caption/useStreamMessageUtils.ts b/template/src/subComponents/caption/useStreamMessageUtils.ts
index d720260b7..086ed401b 100644
--- a/template/src/subComponents/caption/useStreamMessageUtils.ts
+++ b/template/src/subComponents/caption/useStreamMessageUtils.ts
@@ -1,7 +1,9 @@
-import React from 'react';
+import React, {useEffect} from 'react';
import {useCaption} from './useCaption';
import protoRoot from './proto/ptoto';
import PQueue from 'p-queue';
+import {useLocalUid} from '../../../agora-rn-uikit';
+import {useTextToVoice} from '../../utils/useTextToVoice';
type StreamMessageCallback = (args: [number, Uint8Array]) => void;
type FinalListType = {
@@ -17,7 +19,9 @@ const useStreamMessageUtils = (): {
activeSpeakerRef,
prevSpeakerRef,
} = useCaption();
+ const {textToVoice, textToVoice2} = useTextToVoice();
+ const localUid = useLocalUid();
let captionStartTime: number = 0;
const finalList: FinalListType = {};
const finalTranscriptList: FinalListType = {};
@@ -202,6 +206,18 @@ const useStreamMessageUtils = (): {
? existingStringBuffer + ' ' + latestString
: latestString;
+ if (currentFinalText && textstream.uid !== localUid) {
+ console.log(
+ 'debugging new caption text ',
+ currentFinalText,
+ ' spoken by ',
+ textstream.uid,
+ );
+
+ // textToVoice(currentFinalText);
+ textToVoice2(currentFinalText);
+ }
+
// updating the captions
captionText &&
setCaptionObj(prevState => {
diff --git a/template/src/subComponents/recording/useRecording.tsx b/template/src/subComponents/recording/useRecording.tsx
index 1bf0445cc..e8513bfe9 100644
--- a/template/src/subComponents/recording/useRecording.tsx
+++ b/template/src/subComponents/recording/useRecording.tsx
@@ -67,16 +67,31 @@ const getFrontendUrl = (url: string) => {
return url;
};
-interface RecordingsData {
- recordings: [];
- pagination: {};
+export type APIStatus = 'idle' | 'pending' | 'resolved' | 'rejected';
+
+export interface FetchRecordingData {
+ pagination: {
+ limit: number;
+ total: number;
+ page: number;
+ };
+ recordings: {
+ id: string;
+ download_url: string[];
+ title: string;
+ product_name: string;
+ status: 'COMPLETED' | 'STARTED' | 'INPROGRESS' | 'STOPPING';
+ created_at: string;
+ ended_at: string;
+ }[];
}
+
export interface RecordingContextInterface {
startRecording: () => void;
stopRecording: () => void;
isRecordingActive: boolean;
inProgress: boolean;
- fetchRecordings?: (page: number) => Promise;
+ fetchRecordings?: (page: number) => Promise;
deleteRecording?: (id: number) => Promise;
}
diff --git a/template/src/utils/testData.ts b/template/src/utils/testData.ts
new file mode 100644
index 000000000..4ec966b88
--- /dev/null
+++ b/template/src/utils/testData.ts
@@ -0,0 +1,4 @@
+export const AudioData = {
+ audioContent:
+ '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',
+};
diff --git a/template/src/utils/useTextToVoice.ts b/template/src/utils/useTextToVoice.ts
new file mode 100644
index 000000000..d9096ad3c
--- /dev/null
+++ b/template/src/utils/useTextToVoice.ts
@@ -0,0 +1,206 @@
+import {Base64} from '../ai-agent/utils';
+import {AudioData} from './testData';
+
+const RIMI_API_TOKEN = `Rl8b_9inNeP0l4tOoapYOcl_mjnYig7jmbS-5XGuLlo`;
+
+export function useTextToVoice() {
+ function base64ToBinary(base64String) {
+ const binaryString = Base64.atob(base64String);
+ return binaryString;
+ }
+
+ function binaryStringToUint8Array(binaryString) {
+ const len = binaryString.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+ return bytes;
+ }
+
+ function createBlobFromUint8Array(uint8Array) {
+ const blob = new Blob([uint8Array], {type: 'audio/mpeg'});
+ return blob;
+ }
+
+ function createURLFromBlob(blob) {
+ return URL.createObjectURL(blob);
+ }
+
+ function playAudio(audioURL) {
+ const audio = new Audio(audioURL);
+ audio.play();
+ }
+
+ function base64ToMp3(base64String) {
+ const binaryString = base64ToBinary(base64String);
+ const uint8Array = binaryStringToUint8Array(binaryString);
+ const blob = createBlobFromUint8Array(uint8Array);
+ const audioURL = createURLFromBlob(blob);
+ return audioURL;
+ }
+
+ const convertTextToBase64Audio = async text => {
+ try {
+ const options = {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ Authorization: `Bearer ${RIMI_API_TOKEN}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ speaker: 'abbie',
+ text: text,
+ //default is mist
+ //modelId: 'mist',
+ lang: 'eng',
+ audioFormat: 'mp3',
+ samplingRate: 22050,
+ speedAlpha: 1.0,
+ reduceLatency: false,
+ }),
+ };
+ const response = await fetch(
+ 'https://users.rime.ai/v1/rime-tts',
+ options,
+ );
+ const data = await response.json();
+ if (data && data?.audioContent) {
+ return Promise.resolve(data?.audioContent);
+ }
+ } catch (error) {
+ console.error(
+ 'Error on useTextToVoice - convertTextToBase64Audio',
+ error,
+ );
+ return Promise.reject(error);
+ }
+ };
+
+ const textToVoice = async text => {
+ try {
+ //api to convert text to base64 audio data
+ const base64String = await convertTextToBase64Audio(text);
+ //for testing
+ //const base64String = AudioData.audioContent;
+ //base64 to mp3
+ const audioURL = base64ToMp3(base64String);
+
+ //Play the audio
+ playAudio(audioURL);
+
+ return Promise.resolve(audioURL);
+ } catch (error) {
+ console.error('Error on useTextToVoice - textToVoice', error);
+ return Promise.reject(error);
+ }
+ };
+
+ const decodeAndPlayAudio = audioContent => {
+ try {
+ //base64 to mp3
+ const audioURL = base64ToMp3(audioContent);
+ //Play the audio
+ playAudio(audioURL);
+ } catch (error) {
+ console.error('Error on useTextToVoice - decodeAndPlayAudio', error);
+ }
+ };
+
+ const textToVoice3 = async (text: string) => {
+ try {
+ const response = await fetch('http://localhost:3001/tts', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({text}),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch TTS audio');
+ }
+
+ const blob = await response.blob();
+ const audioURL = URL.createObjectURL(blob);
+
+ const audio = new Audio(audioURL);
+ audio.play();
+
+ return Promise.resolve(audioURL);
+ } catch (error) {
+ console.error('Error on useTextToVoice - textToVoice (via proxy)', error);
+ return Promise.reject(error);
+ }
+ };
+
+ const textToVoice2 = async (text: string) => {
+ try {
+ const response = await fetch(
+ 'https://rime-tts-proxy-production.up.railway.app/tts',
+ {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({text}),
+ },
+ );
+
+ if (!response.ok) throw new Error('TTS streaming failed');
+
+ const mediaSource = new MediaSource();
+ const audio = new Audio();
+ audio.src = URL.createObjectURL(mediaSource);
+ audio.play().then(() => {
+ console.log('[TTS] Audio playback started');
+ });
+
+ mediaSource.addEventListener('sourceopen', () => {
+ console.log('[TTS] MediaSource opened');
+ const sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
+
+ const reader = response.body?.getReader();
+ const pump = () => {
+ if (!reader) return;
+ reader.read().then(({done, value}) => {
+ if (done) {
+ console.log('[TTS] All chunks received. Closing stream...');
+ if (!sourceBuffer.updating) mediaSource.endOfStream();
+ return;
+ }
+ if (!value) return;
+
+ console.log(
+ `[TTS] 🔄 Received audio chunk: ${value.byteLength} bytes`,
+ );
+
+ if (!sourceBuffer.updating) {
+ sourceBuffer.appendBuffer(value);
+ pump();
+ } else {
+ sourceBuffer.addEventListener(
+ 'updateend',
+ () => {
+ sourceBuffer.appendBuffer(value);
+ pump();
+ },
+ {once: true},
+ );
+ }
+ });
+ };
+
+ pump();
+ });
+ } catch (error) {
+ console.error('[TTS] Error in streaming text-to-voice:', error);
+ }
+ };
+
+ return {
+ convertTextToBase64Audio,
+ decodeAndPlayAudio,
+ textToVoice,
+ textToVoice2,
+ };
+}
diff --git a/voice-chat.config.json b/voice-chat.config.json
index 8afd8eb68..65bd439bf 100644
--- a/voice-chat.config.json
+++ b/voice-chat.config.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}
diff --git a/voice-chat.config.light.json b/voice-chat.config.light.json
index 06730f2e3..98cf8496c 100644
--- a/voice-chat.config.light.json
+++ b/voice-chat.config.light.json
@@ -78,5 +78,6 @@
"STT_AUTO_START": false,
"CLOUD_RECORDING_AUTO_START": false,
"ENABLE_SPOTLIGHT": false,
- "AUTO_CONNECT_RTM": false
+ "AUTO_CONNECT_RTM": false,
+ "ENABLE_TEXT_TRACKS": true
}