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.
- package/package.json +2 -2
- package/template/agora-rn-uikit/README.md +1 -40
- package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -0
- package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -0
- package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +11 -0
- package/template/agora-rn-uikit/src/Reducer/index.ts +1 -0
- package/template/agora-rn-uikit/src/RtcConfigure.tsx +7 -0
- package/template/bridge/rtc/webNg/RtcEngine.ts +4 -1
- package/template/customization-api/app-state.ts +11 -7
- package/template/customization-api/{customize.ts → customize.tsx} +116 -11
- package/template/customization-api/sub-components.ts +4 -0
- package/template/customization-api/temp.ts +2 -0
- package/template/customization-api/typeDefinition.ts +2 -1
- package/template/customization-api/utils.ts +6 -1
- package/template/defaultConfig.js +4 -2
- package/template/global.d.ts +2 -0
- package/template/src/AppRoutes.tsx +15 -5
- package/template/src/ai-agent/components/AgentControls/AgentContext.tsx +163 -0
- package/template/src/ai-agent/components/AgentControls/LeaveCall.png +0 -0
- package/template/src/ai-agent/components/AgentControls/Vector.svg +3 -0
- package/template/src/ai-agent/components/AgentControls/const.ts +58 -0
- package/template/src/ai-agent/components/AgentControls/index.tsx +293 -0
- package/template/src/ai-agent/components/AudioVisualizer.tsx +91 -0
- package/template/src/ai-agent/components/Bottombar.tsx +91 -0
- package/template/src/ai-agent/components/CustomCreate.tsx +279 -0
- package/template/src/ai-agent/components/CustomCreateNative.tsx +265 -0
- package/template/src/ai-agent/components/CustomSidePanel.tsx +135 -0
- package/template/src/ai-agent/components/FallbackLogo.tsx +80 -0
- package/template/src/ai-agent/components/LocalAudioWave.tsx +171 -0
- package/template/src/ai-agent/components/agent-chat-panel/agent-chat-ui.tsx +82 -0
- package/template/src/ai-agent/components/icons.tsx +227 -0
- package/template/src/ai-agent/components/mobile/Bottombar.tsx +47 -0
- package/template/src/ai-agent/components/mobile/MobileLayoutComponent.tsx +106 -0
- package/template/src/ai-agent/components/mobile/Topbar.tsx +62 -0
- package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/LiveAudioVisualizer.tsx +173 -0
- package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/index.ts +1 -0
- package/template/src/ai-agent/components/react-audio-visualize/LiveAudioVisualizer/utils.ts +102 -0
- package/template/src/ai-agent/components/react-audio-visualize/index.ts +1 -0
- package/template/src/ai-agent/components/utils.ts +15 -0
- package/template/src/ai-agent/index.tsx +301 -0
- package/template/src/ai-agent/routes/CustomLoginRoute.tsx +25 -0
- package/template/src/ai-agent/routes/CustomValidateRoute.tsx +25 -0
- package/template/src/ai-agent/utils.ts +78 -0
- package/template/src/assets/font-styles.css +4 -0
- package/template/src/assets/fonts/icomoon.ttf +0 -0
- package/template/src/assets/selection.json +1 -1
- package/template/src/atoms/CustomIcon.tsx +1 -0
- package/template/src/atoms/ImageIcon.tsx +3 -0
- package/template/src/atoms/ToolbarItem.tsx +0 -2
- package/template/src/components/ChatContext.ts +7 -0
- package/template/src/components/Controls.tsx +6 -1
- package/template/src/components/ErrorBoundary.tsx +37 -0
- package/template/src/components/ErrorBoundaryFallback.tsx +44 -0
- package/template/src/components/RTMConfigure.tsx +25 -20
- package/template/src/components/participants/Participant.tsx +4 -0
- package/template/src/components/participants/UserActionMenuOptions.tsx +34 -1
- package/template/src/components/precall/PermissionHelper.tsx +11 -8
- package/template/src/language/default-labels/videoCallScreenLabels.ts +8 -0
- package/template/src/logger/AppBuilderLogger.tsx +4 -1
- package/template/src/pages/Create.tsx +11 -12
- package/template/src/pages/VideoCall.tsx +1 -0
- package/template/src/pages/video-call/ActionSheet.tsx +33 -29
- package/template/src/pages/video-call/SidePanelHeader.tsx +8 -3
- package/template/src/pages/video-call/SpotlightHighlighter.tsx +91 -0
- package/template/src/pages/video-call/VideoCallMobileView.tsx +17 -6
- package/template/src/pages/video-call/VideoCallScreen.tsx +0 -1
- package/template/src/pages/video-call/VideoRenderer.tsx +32 -4
- package/template/src/rtm-events/constants.ts +2 -0
- package/template/src/subComponents/ChatBubble.tsx +34 -15
- package/template/src/subComponents/FallbackLogo.tsx +3 -1
- package/template/src/subComponents/LocalAudioMute.tsx +20 -2
- package/template/src/utils/index.tsx +3 -4
- package/template/src/utils/useJoinRoom.ts +14 -0
- package/template/src/utils/useSpotlight.ts +31 -0
- 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
|
+
});
|