agora-appbuilder-core 4.0.35 → 4.1.0-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/README.md +1 -40
  3. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -0
  4. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -0
  5. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +11 -0
  6. package/template/agora-rn-uikit/src/Reducer/index.ts +1 -0
  7. package/template/agora-rn-uikit/src/RtcConfigure.tsx +7 -0
  8. package/template/bridge/rtc/webNg/RtcEngine.ts +4 -1
  9. package/template/customization-api/app-state.ts +11 -7
  10. package/template/customization-api/{customize.ts → customize.tsx} +116 -11
  11. package/template/customization-api/sub-components.ts +4 -0
  12. package/template/customization-api/temp.ts +2 -0
  13. package/template/customization-api/typeDefinition.ts +2 -1
  14. package/template/customization-api/utils.ts +6 -1
  15. package/template/defaultConfig.js +4 -2
  16. package/template/global.d.ts +2 -0
  17. package/template/src/AppRoutes.tsx +15 -5
  18. package/template/src/ai-agent/components/AgentControls/AgentContext.tsx +163 -0
  19. package/template/src/ai-agent/components/AgentControls/LeaveCall.png +0 -0
  20. package/template/src/ai-agent/components/AgentControls/Vector.svg +3 -0
  21. package/template/src/ai-agent/components/AgentControls/const.ts +58 -0
  22. package/template/src/ai-agent/components/AgentControls/index.tsx +293 -0
  23. package/template/src/ai-agent/components/AudioVisualizer.tsx +91 -0
  24. package/template/src/ai-agent/components/Bottombar.tsx +91 -0
  25. package/template/src/ai-agent/components/CustomCreate.tsx +279 -0
  26. package/template/src/ai-agent/components/CustomCreateNative.tsx +265 -0
  27. package/template/src/ai-agent/components/CustomSidePanel.tsx +135 -0
  28. package/template/src/ai-agent/components/FallbackLogo.tsx +80 -0
  29. package/template/src/ai-agent/components/LocalAudioWave.tsx +171 -0
  30. package/template/src/ai-agent/components/agent-chat-panel/agent-chat-ui.tsx +82 -0
  31. package/template/src/ai-agent/components/icons.tsx +227 -0
  32. package/template/src/ai-agent/components/mobile/Bottombar.tsx +47 -0
  33. package/template/src/ai-agent/components/mobile/MobileLayoutComponent.tsx +106 -0
  34. package/template/src/ai-agent/components/mobile/Topbar.tsx +62 -0
  35. package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/LiveAudioVisualizer.tsx +173 -0
  36. package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/index.ts +1 -0
  37. package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/utils.ts +102 -0
  38. package/template/src/ai-agent/components/react-audio-visualize/index.ts +1 -0
  39. package/template/src/ai-agent/components/utils.ts +15 -0
  40. package/template/src/ai-agent/index.tsx +301 -0
  41. package/template/src/ai-agent/routes/CustomLoginRoute.tsx +25 -0
  42. package/template/src/ai-agent/routes/CustomValidateRoute.tsx +25 -0
  43. package/template/src/ai-agent/utils.ts +78 -0
  44. package/template/src/assets/font-styles.css +4 -0
  45. package/template/src/assets/fonts/icomoon.ttf +0 -0
  46. package/template/src/assets/selection.json +1 -1
  47. package/template/src/atoms/CustomIcon.tsx +1 -0
  48. package/template/src/atoms/ImageIcon.tsx +3 -0
  49. package/template/src/atoms/ToolbarItem.tsx +0 -2
  50. package/template/src/components/ChatContext.ts +7 -0
  51. package/template/src/components/Controls.tsx +6 -1
  52. package/template/src/components/ErrorBoundary.tsx +37 -0
  53. package/template/src/components/ErrorBoundaryFallback.tsx +44 -0
  54. package/template/src/components/RTMConfigure.tsx +25 -20
  55. package/template/src/components/participants/Participant.tsx +4 -0
  56. package/template/src/components/participants/UserActionMenuOptions.tsx +34 -1
  57. package/template/src/components/precall/PermissionHelper.tsx +11 -8
  58. package/template/src/language/default-labels/videoCallScreenLabels.ts +8 -0
  59. package/template/src/logger/AppBuilderLogger.tsx +4 -1
  60. package/template/src/pages/Create.tsx +11 -12
  61. package/template/src/pages/VideoCall.tsx +1 -0
  62. package/template/src/pages/video-call/ActionSheet.tsx +33 -29
  63. package/template/src/pages/video-call/SidePanelHeader.tsx +8 -3
  64. package/template/src/pages/video-call/SpotlightHighlighter.tsx +91 -0
  65. package/template/src/pages/video-call/VideoCallMobileView.tsx +17 -6
  66. package/template/src/pages/video-call/VideoCallScreen.tsx +0 -1
  67. package/template/src/pages/video-call/VideoRenderer.tsx +32 -4
  68. package/template/src/rtm-events/constants.ts +2 -0
  69. package/template/src/subComponents/ChatBubble.tsx +34 -15
  70. package/template/src/subComponents/FallbackLogo.tsx +3 -1
  71. package/template/src/subComponents/LocalAudioMute.tsx +20 -2
  72. package/template/src/utils/index.tsx +3 -4
  73. package/template/src/utils/useJoinRoom.ts +14 -0
  74. package/template/src/utils/useSpotlight.ts +31 -0
  75. package/template/tsconfig.json +23 -18
@@ -0,0 +1,279 @@
1
+ import React, {useEffect, useState, useContext} from 'react';
2
+ import {View, Text, StyleSheet, TouchableOpacity} from 'react-native';
3
+ import {
4
+ useRoomInfo,
5
+ useCreateRoom,
6
+ useHistory,
7
+ Toast,
8
+ isWebInternal,
9
+ trimText,
10
+ useString,
11
+ Spacer,
12
+ useSetRoomInfo,
13
+ isSDK,
14
+ useStorageContext,
15
+ useErrorContext,
16
+ RoomInfoDefaultValue,
17
+ } from 'customization-api';
18
+ import {Redirect} from '../../components/Router';
19
+ import ThemeConfig from '../../theme';
20
+ import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
21
+ import {AgoraLogo, AgoraOpenAILogo, OpenAILogo, CallIcon} from './icons';
22
+ import {AgentContext} from './AgentControls/AgentContext';
23
+
24
+ const CustomCreate = () => {
25
+ const {
26
+ data: {
27
+ roomId: {host},
28
+ },
29
+ } = useRoomInfo();
30
+ const {setGlobalErrorMessage} = useErrorContext();
31
+ const [loading, setLoading] = useState(false);
32
+ const [roomTitle, onChangeRoomTitle] = useState('');
33
+ const [roomCreated, setRoomCreated] = useState(false);
34
+ const createRoomFun = useCreateRoom();
35
+ const {setRoomInfo} = useSetRoomInfo();
36
+ const {setStore} = useStorageContext();
37
+ const loadingText = useString('loadingText')();
38
+ const history = useHistory();
39
+
40
+ const {agentAuthToken} = useContext(AgentContext);
41
+
42
+ useEffect(() => {
43
+ if (isWebInternal() && !isSDK) {
44
+ document.title = $config.APP_NAME;
45
+ }
46
+ console.log('[SDKEvents] Join listener registered');
47
+ return () => {};
48
+ }, []);
49
+
50
+ // set default room and username
51
+ useEffect(() => {
52
+ const generateChannelId = () => {
53
+ const characters =
54
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
55
+ let result = '';
56
+ for (let i = 0; i < 16; i++) {
57
+ result += characters.charAt(
58
+ Math.floor(Math.random() * characters.length),
59
+ );
60
+ }
61
+ return result;
62
+ };
63
+ setStore(prevState => {
64
+ return {
65
+ ...prevState,
66
+ displayName: 'You',
67
+ };
68
+ });
69
+ // set default meeting name
70
+ onChangeRoomTitle(generateChannelId);
71
+ }, []);
72
+
73
+ // useEffect( () => {
74
+ // // to check if user logged in quer param
75
+ // const queryParams = new URLSearchParams(window.location.search);
76
+ // if (queryParams.get('auth') === 'success' && roomTitle != '') {
77
+ // createRoomAndNavigateToShare(
78
+ // roomTitle?.trim(),
79
+ // false,
80
+ // false
81
+ // );
82
+ // }
83
+ // },[roomTitle])
84
+
85
+ const createRoomAndNavigateToShare = async (
86
+ roomTitle: string,
87
+ enablePSTN: boolean,
88
+ isSeparateHostLink: boolean,
89
+ ) => {
90
+ if (roomTitle !== '') {
91
+ setLoading(true);
92
+ try {
93
+ setRoomInfo(RoomInfoDefaultValue);
94
+ //@ts-ignore
95
+ //isSeparateHostLink will be for internal usage since backend integration is not there
96
+ await createRoomFun(roomTitle, enablePSTN, isSeparateHostLink);
97
+
98
+ setLoading(false);
99
+ Toast.show({
100
+ leadingIconName: 'tick-fill',
101
+ type: 'success',
102
+ text1: 'You have joined channel ' + trimText(roomTitle),
103
+ text2: null,
104
+ visibilityTime: 3000,
105
+ primaryBtn: null,
106
+ secondaryBtn: null,
107
+ leadingIcon: null,
108
+ });
109
+ setRoomCreated(true);
110
+ } catch (error) {
111
+ setLoading(false);
112
+ setGlobalErrorMessage({
113
+ name: 'Unable to join channel',
114
+ message: error.message,
115
+ });
116
+ }
117
+ }
118
+ };
119
+ const showError = () => {
120
+ Toast.show({
121
+ leadingIconName: 'alert',
122
+ type: 'error',
123
+ text1: 'Backend endpoint not configured',
124
+ text2: 'Please configure backend endpoint config.json',
125
+ visibilityTime: 1000 * 10,
126
+ primaryBtn: null,
127
+ secondaryBtn: null,
128
+ leadingIcon: null,
129
+ });
130
+ };
131
+
132
+ return (
133
+ <>
134
+ {!roomCreated ? (
135
+ <View style={style.root}>
136
+ <View style={style.topLogoContainer}>
137
+ <View style={{paddingTop: 14}}>
138
+ <AgoraLogo />
139
+ </View>
140
+ <View>
141
+ <OpenAILogo />
142
+ </View>
143
+ </View>
144
+ <View
145
+ style={{
146
+ width: 490,
147
+ marginTop: 90,
148
+ display: 'flex',
149
+ flexDirection: 'column',
150
+ justifyContent: 'center',
151
+ alignItems: 'center',
152
+ }}>
153
+ <View style={style.centerLogoContainer}>
154
+ <View style={{padding: 20}}>
155
+ <AgoraOpenAILogo />
156
+ </View>
157
+ <Text style={style.mainTextStyle}>Agora & Conversational AI</Text>
158
+ <Spacer size={20} />
159
+ <Text style={style.subTextStyle}>
160
+ AI Builder Conversational AI demo
161
+ </Text>
162
+ </View>
163
+ <Spacer size={20} />
164
+ <TouchableOpacity
165
+ disabled={loading || !roomTitle?.trim()}
166
+ style={style.btnContainer}
167
+ onPress={() => {
168
+ const queryParams = new URLSearchParams(window.location.search);
169
+ if (!$config.BACKEND_ENDPOINT) {
170
+ showError();
171
+ } else {
172
+ // handleSSOLogin()
173
+ //history.push('/login')
174
+ createRoomAndNavigateToShare(roomTitle?.trim(), false, false);
175
+ }
176
+ }}>
177
+ <CallIcon fill="#111111" />
178
+ <Text
179
+ style={{
180
+ textAlign: 'center',
181
+ fontSize: 18,
182
+ fontStyle: 'normal',
183
+ fontWeight: '600',
184
+ lineHeight: 18,
185
+ }}>
186
+ {loading ? loadingText : 'Sign in to Join Call'}
187
+ </Text>
188
+ </TouchableOpacity>
189
+ </View>
190
+ </View>
191
+ ) : (
192
+ <Redirect to={host} />
193
+ )}
194
+ </>
195
+ );
196
+ };
197
+
198
+ const style = StyleSheet.create({
199
+ root: {
200
+ display: 'flex',
201
+ flex: 1,
202
+ flexDirection: 'column',
203
+ padding: 20,
204
+ alignItems: 'center',
205
+ gap: 20,
206
+ backgroundColor: '#111',
207
+ borderRadius: 20,
208
+ },
209
+ mainMobile: {
210
+ paddingBottom: 80,
211
+ paddingTop: 66,
212
+ flex: 1,
213
+ },
214
+ mobileContainerStyle: {
215
+ backgroundColor: 'transparent',
216
+ flex: 1,
217
+ paddingBottom: 0,
218
+ paddingTop: 0,
219
+ },
220
+ btnContainer: {
221
+ width: '100%',
222
+ alignItems: 'center',
223
+ display: 'flex',
224
+ flexDirection: 'row',
225
+ height: 56,
226
+ padding: 20,
227
+ justifyContent: 'center',
228
+ alignContent: 'center',
229
+ gap: 8,
230
+ borderRadius: 4,
231
+ backgroundColor: '#00C2FF',
232
+ },
233
+ separator: {
234
+ height: 1,
235
+ },
236
+ topLogoContainer: {
237
+ paddingVertical: 20,
238
+ paddingHorizontal: 32,
239
+ display: 'flex',
240
+ width: 'auto',
241
+ flexDirection: 'row',
242
+ height: 60,
243
+ justifyContent: 'center',
244
+ alignItems: 'center',
245
+ gap: 20,
246
+ flexShrink: 0,
247
+ alignSelf: 'stretch',
248
+ },
249
+ centerLogoContainer: {
250
+ padding: 40,
251
+ display: 'flex',
252
+ paddingHorizontal: 8,
253
+ flexFirection: 'column',
254
+ justifyContent: 'center',
255
+ alignItems: 'center',
256
+ alignSelf: 'stretch',
257
+ },
258
+ mainTextStyle: {
259
+ color: '#FFF',
260
+ textAlign: 'center',
261
+ fontFamily: ThemeConfig.FontFamily.sansPro,
262
+ fontSize: 40,
263
+ fontStyle: 'normal',
264
+ fontWeight: '600',
265
+ lineHeight: 40,
266
+ letterSpacing: 1,
267
+ },
268
+ subTextStyle: {
269
+ color: '#FFFFFF' + hexadecimalTransparency['70%'],
270
+ textAlign: 'center',
271
+ fontFamily: ThemeConfig.FontFamily.sansPro,
272
+ fontSize: 18,
273
+ fontStyle: 'normal',
274
+ fontWeight: '400',
275
+ lineHeight: 21.6,
276
+ },
277
+ });
278
+
279
+ export default CustomCreate;
@@ -0,0 +1,265 @@
1
+ import React, {useEffect, useState, useContext} from 'react';
2
+ import {View, Text, StyleSheet, TouchableOpacity} from 'react-native';
3
+ import {
4
+ Redirect,
5
+ Toast,
6
+ useErrorContext,
7
+ isWebInternal,
8
+ trimText,
9
+ useString,
10
+ useCreateRoom,
11
+ RoomInfoDefaultValue,
12
+ useRoomInfo,
13
+ Card,
14
+ useSetRoomInfo,
15
+ isSDK,
16
+ useHistory,
17
+ useStorageContext,
18
+ } from 'customization-api';
19
+ import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
20
+ import {AgoraLogo, AgoraOpenAILogo, OpenAILogo, CallIcon} from './icons';
21
+ import {AgentContext} from './AgentControls/AgentContext';
22
+ import ThemeConfig from '../../theme';
23
+
24
+ const CustomCreateNative = () => {
25
+ const {
26
+ data: {
27
+ roomId: {host},
28
+ },
29
+ } = useRoomInfo();
30
+ const {setGlobalErrorMessage} = useErrorContext();
31
+ const [loading, setLoading] = useState(false);
32
+ const [roomTitle, onChangeRoomTitle] = useState('');
33
+ const [roomCreated, setRoomCreated] = useState(false);
34
+ const createRoomFun = useCreateRoom();
35
+ const {setRoomInfo} = useSetRoomInfo();
36
+ const {setStore} = useStorageContext();
37
+ const loadingText = useString('loadingText')();
38
+ const history = useHistory();
39
+ const {agentAuthToken} = useContext(AgentContext);
40
+
41
+ useEffect(() => {
42
+ if (isWebInternal() && !isSDK) {
43
+ document.title = $config.APP_NAME;
44
+ }
45
+ console.log('[SDKEvents] Join listener registered');
46
+ return () => {};
47
+ }, []);
48
+
49
+ // set default room and username
50
+ useEffect(() => {
51
+ const generateChannelId = () => {
52
+ const characters =
53
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
54
+ let result = '';
55
+ for (let i = 0; i < 16; i++) {
56
+ result += characters.charAt(
57
+ Math.floor(Math.random() * characters.length),
58
+ );
59
+ }
60
+ return result;
61
+ };
62
+ setStore(prevState => {
63
+ return {
64
+ ...prevState,
65
+ displayName: 'You',
66
+ };
67
+ });
68
+ // set default meeting name
69
+ onChangeRoomTitle(generateChannelId);
70
+ }, []);
71
+
72
+ useEffect(() => {
73
+ // to check if user logged in quer param
74
+ const queryParams = new URLSearchParams(window.location.search);
75
+ if (queryParams.get('auth') === 'success' && roomTitle != '') {
76
+ createRoomAndNavigateToShare(roomTitle?.trim(), false, false);
77
+ }
78
+ }, [roomTitle]);
79
+
80
+ const createRoomAndNavigateToShare = async (
81
+ roomTitle: string,
82
+ enablePSTN: boolean,
83
+ isSeparateHostLink: boolean,
84
+ ) => {
85
+ if (roomTitle !== '') {
86
+ setLoading(true);
87
+ try {
88
+ setRoomInfo(RoomInfoDefaultValue);
89
+ //@ts-ignore
90
+ //isSeparateHostLink will be for internal usage since backend integration is not there
91
+ await createRoomFun(roomTitle, enablePSTN, isSeparateHostLink);
92
+
93
+ setLoading(false);
94
+ Toast.show({
95
+ leadingIconName: 'tick-fill',
96
+ type: 'success',
97
+ text1: 'You have joined channel ' + trimText(roomTitle),
98
+ text2: null,
99
+ visibilityTime: 3000,
100
+ primaryBtn: null,
101
+ secondaryBtn: null,
102
+ leadingIcon: null,
103
+ });
104
+ setRoomCreated(true);
105
+ } catch (error) {
106
+ setLoading(false);
107
+
108
+ setGlobalErrorMessage({
109
+ name: 'Unable to join Channel',
110
+ message: error.message,
111
+ });
112
+ }
113
+ }
114
+ };
115
+ const showError = () => {
116
+ Toast.show({
117
+ leadingIconName: 'alert',
118
+ type: 'error',
119
+ text1: 'Backend endpoint not configured',
120
+ text2: 'Please configure backend endpoint config.json',
121
+ visibilityTime: 1000 * 10,
122
+ primaryBtn: null,
123
+ secondaryBtn: null,
124
+ leadingIcon: null,
125
+ });
126
+ };
127
+
128
+ return (
129
+ <>
130
+ {!roomCreated && !agentAuthToken && (
131
+ <View style={style.root}>
132
+ <View style={style.mainMobile}>
133
+ <Card cardContainerStyle={style.mobileContainerStyle}>
134
+ <View>
135
+ <View style={style.topLogoContainer}>
136
+ <View style={{paddingTop: 14}}>
137
+ <AgoraLogo />
138
+ </View>
139
+ <View>
140
+ <OpenAILogo />
141
+ </View>
142
+ </View>
143
+ <View style={style.centerLogoContainer}>
144
+ <AgoraOpenAILogo />
145
+ <Text style={style.mainTextStyle}>Agora & OpenAI</Text>
146
+ <Text style={style.subTextStyle}>
147
+ Agora Conversational AI demo built in partnership with
148
+ OpenAI
149
+ </Text>
150
+ </View>
151
+ </View>
152
+ <TouchableOpacity
153
+ disabled={loading || !roomTitle?.trim()}
154
+ style={style.btnContainer}
155
+ onPress={() => {
156
+ const queryParams = new URLSearchParams(
157
+ window.location.search,
158
+ );
159
+ if (!$config.BACKEND_ENDPOINT) {
160
+ showError();
161
+ } else {
162
+ // handleSSOLogin()
163
+ history.push('/login');
164
+ }
165
+ }}>
166
+ <CallIcon fill="#111111" />
167
+ <Text
168
+ style={{
169
+ textAlign: 'center',
170
+ fontSize: 18,
171
+ fontStyle: 'normal',
172
+ fontWeight: '600',
173
+ lineHeight: 18,
174
+ }}>
175
+ {loading ? loadingText : 'Sign in to Join Call'}
176
+ </Text>
177
+ </TouchableOpacity>
178
+ </Card>
179
+ </View>
180
+ </View>
181
+ )}
182
+ {roomCreated && agentAuthToken && <Redirect to={host} />}
183
+ </>
184
+ );
185
+ };
186
+
187
+ const style = StyleSheet.create({
188
+ root: {
189
+ flex: 1,
190
+ },
191
+ main: {
192
+ flexGrow: 1,
193
+ flexDirection: 'row',
194
+ justifyContent: 'center',
195
+ },
196
+ mainMobile: {
197
+ paddingBottom: 80,
198
+ paddingTop: 66,
199
+ flex: 1,
200
+ },
201
+ mobileContainerStyle: {
202
+ backgroundColor: 'transparent',
203
+ flex: 1,
204
+ paddingBottom: 0,
205
+ paddingTop: 0,
206
+ },
207
+ btnContainer: {
208
+ width: '100%',
209
+ alignItems: 'center',
210
+ display: 'flex',
211
+ flexDirection: 'row',
212
+ height: 56,
213
+ padding: 20,
214
+ justifyContent: 'center',
215
+ alignContent: 'center',
216
+ gap: 8,
217
+ borderRadius: 4,
218
+ backgroundColor: '#00C2FF',
219
+ },
220
+ separator: {
221
+ height: 1,
222
+ },
223
+ topLogoContainer: {
224
+ display: 'flex',
225
+ flex: 1,
226
+ flexDirection: 'row',
227
+ height: 60,
228
+ justifyContent: 'center',
229
+ alignItems: 'center',
230
+ gap: 20,
231
+ flexShrink: 0,
232
+ },
233
+ centerLogoContainer: {
234
+ paddingTop: 135,
235
+ paddingBottom: 10,
236
+ display: 'flex',
237
+ paddingHorizontal: 8,
238
+ flexFirection: 'column',
239
+ justifyContent: 'center',
240
+ alignItems: 'center',
241
+ gap: 12,
242
+ alignSelf: 'stretch',
243
+ },
244
+ mainTextStyle: {
245
+ color: '#FFF',
246
+ textAlign: 'center',
247
+ fontFamily: ThemeConfig.FontFamily.sansPro,
248
+ fontSize: 40,
249
+ fontStyle: 'normal',
250
+ fontWeight: '600',
251
+ lineHeight: 40,
252
+ letterSpacing: 1,
253
+ },
254
+ subTextStyle: {
255
+ color: '#FFFFFF' + hexadecimalTransparency['70%'],
256
+ textAlign: 'center',
257
+ fontFamily: ThemeConfig.FontFamily.sansPro,
258
+ fontSize: 18,
259
+ fontStyle: 'normal',
260
+ fontWeight: '400',
261
+ lineHeight: 21.6,
262
+ },
263
+ });
264
+
265
+ export default CustomCreateNative;
@@ -0,0 +1,135 @@
1
+ import {StyleSheet} from 'react-native';
2
+ import React, {useContext} from 'react';
3
+ import {useRtc} from 'customization-api';
4
+ import {AgentContext} from './AgentControls/AgentContext';
5
+ import ChatScreen from './agent-chat-panel/agent-chat-ui';
6
+
7
+ const CustomSidePanel = () => {
8
+ const {RtcEngineUnsafe} = useRtc();
9
+ const {isSubscribedForStreams, setIsSubscribedForStreams, addChatItem} =
10
+ useContext(AgentContext);
11
+
12
+ const messageCache = {};
13
+ const TIMEOUT_MS = 5000; // Timeout for incomplete messages
14
+
15
+ React.useEffect(() => {
16
+ if (!isSubscribedForStreams) {
17
+ RtcEngineUnsafe.addListener(
18
+ 'onStreamMessage',
19
+ handleStreamMessageCallback,
20
+ );
21
+ setIsSubscribedForStreams(true);
22
+ }
23
+ }, []);
24
+
25
+ const handleStreamMessageCallback = (...args) => {
26
+ console.log('rec', args);
27
+ parseData(args[1]);
28
+ };
29
+
30
+ const parseData = data => {
31
+ let decoder = new TextDecoder('utf-8');
32
+ let decodedMessage = decoder.decode(data);
33
+ console.log('[test] textstream raw data', decodedMessage);
34
+ handleChunk(decodedMessage);
35
+ };
36
+ // Function to process received chunk via event emitter
37
+ const handleChunk = (formattedChunk: string) => {
38
+ try {
39
+ // Split the chunk by the delimiter "|"
40
+ const [message_id, partIndexStr, totalPartsStr, content] =
41
+ formattedChunk.split('|');
42
+
43
+ const part_index = parseInt(partIndexStr, 10);
44
+ const total_parts =
45
+ totalPartsStr === '???' ? -1 : parseInt(totalPartsStr, 10); // -1 means total parts unknown
46
+
47
+ // Ensure total_parts is known before processing further
48
+ if (total_parts === -1) {
49
+ console.warn(
50
+ `Total parts for message ${message_id} unknown, waiting for further parts.`,
51
+ );
52
+ return;
53
+ }
54
+
55
+ const chunkData = {
56
+ message_id,
57
+ part_index,
58
+ total_parts,
59
+ content,
60
+ };
61
+
62
+ // Check if we already have an entry for this message
63
+ if (!messageCache[message_id]) {
64
+ messageCache[message_id] = [];
65
+ // Set a timeout to discard incomplete messages
66
+ setTimeout(() => {
67
+ if (messageCache[message_id]?.length !== total_parts) {
68
+ console.warn(`Incomplete message with ID ${message_id} discarded`);
69
+ delete messageCache[message_id]; // Discard incomplete message
70
+ }
71
+ }, TIMEOUT_MS);
72
+ }
73
+
74
+ // Cache this chunk by message_id
75
+ messageCache[message_id].push(chunkData);
76
+
77
+ // If all parts are received, reconstruct the message
78
+ if (messageCache[message_id].length === total_parts) {
79
+ const completeMessage = reconstructMessage(messageCache[message_id]);
80
+ const data = atob(completeMessage);
81
+ const {stream_id, is_final, text, text_ts} = JSON.parse(data);
82
+ /** Data type of above object
83
+ * stream_id: number
84
+ * is_final: boolean
85
+ * text: string
86
+ * text_ts: number
87
+ */
88
+ const textItem = {
89
+ id: message_id,
90
+ uid: stream_id,
91
+ time: text_ts,
92
+ dataType: 'transcribe',
93
+ text: text,
94
+ isFinal: is_final,
95
+ isSelf: stream_id === 0 ? false : true,
96
+ };
97
+
98
+ if (text.trim().length > 0) {
99
+ //this.emit("textChanged", textItem);
100
+ console.warn('emit textChanged: ', textItem);
101
+ addChatItem(textItem);
102
+ }
103
+
104
+ // Clean up the cache
105
+ delete messageCache[message_id];
106
+ }
107
+ } catch (error) {
108
+ console.error('Error processing chunk:', error);
109
+ }
110
+ };
111
+
112
+ const reconstructMessage = chunks => {
113
+ // Sort chunks by their part index
114
+ chunks.sort((a, b) => a.part_index - b.part_index);
115
+
116
+ // Concatenate all chunks to form the full message
117
+ return chunks.map(chunk => chunk.content).join('');
118
+ };
119
+
120
+ return (
121
+ <div style={styles.container}>
122
+ <ChatScreen />
123
+ </div>
124
+ );
125
+ };
126
+
127
+ export default CustomSidePanel;
128
+
129
+ const styles = StyleSheet.create({
130
+ container: {
131
+ flex: 1,
132
+ display: 'flex',
133
+ height: '100%',
134
+ },
135
+ });