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,106 @@
1
+ import React, {useContext, useEffect, useState} from 'react';
2
+ import {View} from 'react-native';
3
+ import {ILocalAudioTrack, IRemoteAudioTrack} from 'agora-rtc-sdk-ng';
4
+ import {
5
+ MaxVideoView,
6
+ useContent,
7
+ useLocalUid,
8
+ LayoutComponent,
9
+ useRtc,
10
+ useLocalAudio,
11
+ useIsAudioEnabled,
12
+ } from 'customization-api';
13
+ import AudioVisualizer, {DisconnectedView} from '../AudioVisualizer';
14
+ import {ActiveSpeakerAnimation} from '../../components/LocalAudioWave';
15
+ import {AgentContext} from '../AgentControls/AgentContext';
16
+
17
+ const MobileLayoutComponent: LayoutComponent = () => {
18
+ const localUid = useLocalUid();
19
+ const {defaultContent, activeUids} = useContent();
20
+ const {RtcEngineUnsafe} = useRtc();
21
+ const [localTracks, setLocalTrack] = useState<ILocalAudioTrack | null>(null);
22
+ const [remoteTrack, setRemoteTrack] = useState<IRemoteAudioTrack | null>(
23
+ null,
24
+ );
25
+ const {agentUID} = useContext(AgentContext);
26
+
27
+ const {getLocalAudioStream, getRemoteAudioStream} = useLocalAudio();
28
+ const isAudioEnabled = useIsAudioEnabled();
29
+ const connected = activeUids.includes(agentUID);
30
+ console.log({activeUids}, 'active uids');
31
+
32
+ useEffect(() => {
33
+ if (getLocalAudioStream()) {
34
+ setLocalTrack(getLocalAudioStream());
35
+ }
36
+ }, [RtcEngineUnsafe]);
37
+
38
+ useEffect(() => {}, [isAudioEnabled]);
39
+
40
+ useEffect(() => {
41
+ if (getRemoteAudioStream(agentUID)) {
42
+ setRemoteTrack(getRemoteAudioStream(agentUID));
43
+ }
44
+ }, [activeUids]);
45
+
46
+ return (
47
+ <View
48
+ style={{
49
+ flex: 1,
50
+ display: 'flex',
51
+ flexDirection: 'column',
52
+ }}>
53
+ <MaxVideoView
54
+ containerStyle={{borderRadius: 12, borderWidth: 0}}
55
+ innerContainerStyle={{backgroundColor: '#222'}}
56
+ user={{
57
+ ...defaultContent[agentUID],
58
+ name: 'OpenAI',
59
+ video: false,
60
+ }}
61
+ CustomChild={() =>
62
+ connected ? (
63
+ <AudioVisualizer audioTrack={remoteTrack} />
64
+ ) : (
65
+ <DisconnectedView isConnected={connected} />
66
+ )
67
+ }
68
+ hideMenuOptions={true}
69
+ />
70
+ <View
71
+ style={{
72
+ position: 'absolute',
73
+ top: 8,
74
+ right: 8,
75
+ display: 'flex',
76
+ flexDirection: 'row',
77
+ height: 100,
78
+ width: 215,
79
+ }}>
80
+ <MaxVideoView
81
+ containerStyle={{borderRadius: 8, borderWidth: 0}}
82
+ user={defaultContent[localUid]}
83
+ avatarRadius={48}
84
+ hideMenuOptions={true}
85
+ />
86
+ <View
87
+ style={{
88
+ position: 'absolute',
89
+ width: 100,
90
+ height: 50,
91
+ bottom: 16,
92
+ right: -38,
93
+ }}>
94
+ {localTracks && isAudioEnabled(localUid) && (
95
+ <ActiveSpeakerAnimation
96
+ audioTrack={localTracks}
97
+ isMuted={!isAudioEnabled(localUid)}
98
+ />
99
+ )}
100
+ </View>
101
+ </View>
102
+ </View>
103
+ );
104
+ };
105
+
106
+ export default MobileLayoutComponent;
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import {View, StyleSheet, Text, TouchableOpacity} from 'react-native';
3
+ import {LogoIcon, SettingsIcon} from '../icons';
4
+ import {SidePanelType, useSidePanel} from 'customization-api';
5
+ import ThemeConfig from '../../../theme';
6
+
7
+ const MobileTopbar = () => {
8
+ const {setSidePanel} = useSidePanel();
9
+ return (
10
+ <View style={style.rootStyle}>
11
+ <View style={style.containerStyle}>
12
+ <View style={style.logContainerStyle}>
13
+ <LogoIcon />
14
+ </View>
15
+ <Text style={style.textStyle}>Agora & OpenAI Demo</Text>
16
+ </View>
17
+ <TouchableOpacity
18
+ onPress={() => {
19
+ setSidePanel(SidePanelType.Settings);
20
+ }}>
21
+ <SettingsIcon />
22
+ </TouchableOpacity>
23
+ </View>
24
+ );
25
+ };
26
+
27
+ const style = StyleSheet.create({
28
+ rootStyle: {
29
+ display: 'flex',
30
+ flexDirection: 'row',
31
+ justifyContent: 'space-between',
32
+ alignItems: 'center',
33
+ paddingBottom: 20,
34
+ },
35
+ containerStyle: {
36
+ display: 'flex',
37
+ flexDirection: 'row',
38
+ justifyContent: 'center',
39
+ alignItems: 'center',
40
+ gap: 4,
41
+ alignSelf: 'stretch',
42
+ },
43
+ logContainerStyle: {
44
+ display: 'flex',
45
+ width: 40,
46
+ height: 40,
47
+ paddingRight: 1,
48
+ justifyContent: 'center',
49
+ alignItems: 'center',
50
+ },
51
+ textStyle: {
52
+ color: '#C3C3C3',
53
+ textAlign: 'center',
54
+ fontFamily: ThemeConfig.FontFamily.sansPro,
55
+ fontSize: 18,
56
+ fontStyle: 'normal',
57
+ fontWeight: '600',
58
+ lineHeight: 18,
59
+ },
60
+ });
61
+
62
+ export default MobileTopbar;
@@ -0,0 +1,173 @@
1
+ import React, {
2
+ type ReactElement,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import {calculateBarData, draw} from './utils';
9
+ import {ILocalAudioTrack, IRemoteAudioTrack} from 'agora-rtc-sdk-ng';
10
+
11
+ export interface Props {
12
+ /**
13
+ * Width of the visualization. Default: "100%"
14
+ */
15
+ width?: number | string;
16
+ /**
17
+ * Height of the visualization. Default: "100%"
18
+ */
19
+ height?: number | string;
20
+ /**
21
+ * Width of each individual bar in the visualization. Default: `2`
22
+ */
23
+ barWidth?: number;
24
+ /**
25
+ * Gap between each bar in the visualization. Default: `1`
26
+ */
27
+ gap?: number;
28
+ /**
29
+ * BackgroundColor for the visualization: Default: `transparent`
30
+ */
31
+ backgroundColor?: string;
32
+ /**
33
+ * Color of the bars drawn in the visualization. Default: `"rgb(160, 198, 255)"`
34
+ */
35
+ barColor?: string;
36
+ /**
37
+ * An unsigned integer, representing the window size of the FFT, given in number of samples.
38
+ * Default: `1024`
39
+ */
40
+ fftSize?:
41
+ | 32
42
+ | 64
43
+ | 128
44
+ | 256
45
+ | 512
46
+ | 1024
47
+ | 2048
48
+ | 4096
49
+ | 8192
50
+ | 16384
51
+ | 32768;
52
+ /**
53
+ * A double, representing the maximum decibel value for scaling the FFT analysis data
54
+ * Default: `-10`
55
+ */
56
+ maxDecibels?: number;
57
+ /**
58
+ * A double, representing the minimum decibel value for scaling the FFT analysis data
59
+ * Default: `-90`
60
+ */
61
+ minDecibels?: number;
62
+ /**
63
+ * A double within the range 0 to 1 (0 meaning no time averaging). The default value is 0.8.
64
+ * Default: `0.4`
65
+ */
66
+ smoothingTimeConstant?: number;
67
+ audioTrack?: ILocalAudioTrack | IRemoteAudioTrack;
68
+ }
69
+
70
+ const LiveAudioVisualizer: (props: Props) => ReactElement = ({
71
+ width = '100%',
72
+ height = '100%',
73
+ barWidth = 2,
74
+ gap = 1,
75
+ backgroundColor = 'transparent',
76
+ barColor = 'rgb(160, 198, 255)',
77
+ fftSize = 1024,
78
+ maxDecibels = -10,
79
+ minDecibels = -90,
80
+ smoothingTimeConstant = 0.4,
81
+ audioTrack,
82
+ }: Props) => {
83
+ const [audioContext, setAudioContext] = useState<AudioContext | null>(null);
84
+ const [audioSource, setAudioSource] = useState<MediaStreamAudioSourceNode>();
85
+ const [analyser, setAnalyser] = useState<AnalyserNode>();
86
+ const canvasRef = useRef<HTMLCanvasElement>(null);
87
+
88
+ useEffect(() => {
89
+ if (!audioTrack) {
90
+ // Clean up if track is null
91
+ if (audioContext) {
92
+ audioContext.state !== 'closed' && audioContext.close();
93
+ setAudioContext(null);
94
+ setAnalyser(null);
95
+ setAudioSource(null);
96
+ }
97
+ return;
98
+ }
99
+
100
+ const newAudioContext = new (window.AudioContext ||
101
+ (window as any).webkitAudioContext)();
102
+ const newAnalyser = newAudioContext.createAnalyser();
103
+ newAnalyser.fftSize = fftSize;
104
+ newAnalyser.minDecibels = minDecibels;
105
+ newAnalyser.maxDecibels = maxDecibels;
106
+ newAnalyser.smoothingTimeConstant = smoothingTimeConstant;
107
+
108
+ const source = newAudioContext.createMediaStreamSource(
109
+ new MediaStream([audioTrack.getMediaStreamTrack()]),
110
+ );
111
+ source.connect(newAnalyser);
112
+
113
+ setAudioContext(newAudioContext);
114
+ setAnalyser(newAnalyser);
115
+ setAudioSource(source);
116
+
117
+ return () => {
118
+ source.disconnect();
119
+ newAnalyser.disconnect();
120
+ newAudioContext.state !== 'closed' && newAudioContext.close();
121
+ };
122
+ }, [audioTrack, fftSize, minDecibels, maxDecibels, smoothingTimeConstant]);
123
+
124
+ useEffect(() => {
125
+ if (analyser && audioTrack) {
126
+ requestAnimationFrame(report);
127
+ }
128
+ }, [analyser, audioTrack]);
129
+
130
+ const report = useCallback(() => {
131
+ if (!analyser || !audioContext || !audioTrack) return;
132
+
133
+ const data = new Uint8Array(analyser.frequencyBinCount);
134
+ analyser.getByteFrequencyData(data);
135
+ processFrequencyData(data);
136
+
137
+ if (audioTrack.isPlaying) {
138
+ requestAnimationFrame(report);
139
+ }
140
+ }, [analyser, audioContext, audioTrack]);
141
+
142
+ const processFrequencyData = (data: Uint8Array): void => {
143
+ if (!canvasRef.current) return;
144
+
145
+ const dataPoints = calculateBarData(
146
+ data,
147
+ canvasRef.current.width,
148
+ barWidth,
149
+ gap,
150
+ );
151
+ draw(
152
+ dataPoints,
153
+ canvasRef.current,
154
+ barWidth,
155
+ gap,
156
+ backgroundColor,
157
+ barColor,
158
+ );
159
+ };
160
+
161
+ return (
162
+ <canvas
163
+ ref={canvasRef}
164
+ width={width}
165
+ height={height}
166
+ style={{
167
+ aspectRatio: 'unset',
168
+ }}
169
+ />
170
+ );
171
+ };
172
+
173
+ export {LiveAudioVisualizer};
@@ -0,0 +1 @@
1
+ export {LiveAudioVisualizer} from './LiveAudioVisualizer';
@@ -0,0 +1,102 @@
1
+ interface CustomCanvasRenderingContext2D extends CanvasRenderingContext2D {
2
+ roundRect: (
3
+ x: number,
4
+ y: number,
5
+ w: number,
6
+ h: number,
7
+ radius: number,
8
+ ) => void;
9
+ }
10
+
11
+ export const calculateBarData = (
12
+ frequencyData: Uint8Array,
13
+ width: number,
14
+ barWidth: number,
15
+ gap: number,
16
+ ): number[] => {
17
+ let units = width / (barWidth + gap);
18
+ let step = Math.floor(frequencyData.length / units);
19
+
20
+ if (units > frequencyData.length) {
21
+ units = frequencyData.length;
22
+ step = 1;
23
+ }
24
+
25
+ const data: number[] = [];
26
+
27
+ for (let i = 0; i < units; i++) {
28
+ let sum = 0;
29
+
30
+ for (let j = 0; j < step && i * step + j < frequencyData.length; j++) {
31
+ sum += frequencyData[i * step + j];
32
+ }
33
+ data.push(sum / step);
34
+ }
35
+ return data;
36
+ };
37
+
38
+ export const draw = (
39
+ data: number[],
40
+ canvas: HTMLCanvasElement,
41
+ barWidth: number,
42
+ gap: number,
43
+ backgroundColor: string,
44
+ barColor: string,
45
+ ): void => {
46
+ const amp = canvas.height / 2;
47
+
48
+ const ctx = canvas.getContext('2d') as CustomCanvasRenderingContext2D;
49
+ if (!ctx) return;
50
+
51
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
52
+
53
+ if (backgroundColor !== 'transparent') {
54
+ ctx.fillStyle = backgroundColor;
55
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
56
+ }
57
+
58
+ // const sampled = data.slice(0, 8);
59
+ //
60
+ // const downSampled = new Array(5).fill(0);
61
+ //
62
+ // const downPoint = Math.floor(sampled.length / 5);
63
+ //
64
+ // sampled.forEach((dp, i) => {
65
+ // downSampled[Math.floor(i / downPoint)] += dp;
66
+ // });
67
+
68
+ // const sampled = new Array(Math.ceil(data.length / 5)).fill(0);
69
+
70
+ // for (const dataPoint of data) {
71
+ // const index = Math.floor(dataPoint / 5);
72
+ // sampled[index] += dataPoint;
73
+ // }
74
+
75
+ const displayData = data.slice(0, 5);
76
+
77
+ const newBarWidth =
78
+ (canvas.width - gap * (displayData.length - 1)) / displayData.length;
79
+
80
+ const minHeight = newBarWidth;
81
+
82
+ displayData.forEach((dpo, i) => {
83
+ const dp = dpo;
84
+
85
+ ctx.fillStyle = barColor;
86
+
87
+ const x = i * (newBarWidth + gap);
88
+ const y = amp - dp / 2;
89
+ const w = newBarWidth;
90
+ const h = dp || 1;
91
+
92
+ ctx.beginPath();
93
+ if (ctx.roundRect) {
94
+ // making sure roundRect is supported by the browser
95
+ ctx.roundRect(x, y - minHeight / 2, w, h + minHeight, w / 2);
96
+ ctx.fill();
97
+ } else {
98
+ // fallback for browsers that do not support roundRect
99
+ ctx.fillRect(x, y, w, h);
100
+ }
101
+ });
102
+ };
@@ -0,0 +1 @@
1
+ export {LiveAudioVisualizer} from './LiveAudioVisualizer';
@@ -0,0 +1,15 @@
1
+ import {
2
+ AGENT_PROXY_URL,
3
+ AGORA_SSO_LOGIN_PATH,
4
+ AGORA_SSO_CLIENT_ID,
5
+ AGORA_SSO_BASE,
6
+ } from '../components/AgentControls/const';
7
+
8
+ export const handleSSOLogin = () => {
9
+ const REDIRECT_URL = `${AGENT_PROXY_URL}/login`;
10
+ const originURL = window.location.origin + '/validate';
11
+
12
+ const ssoUrl = `${AGORA_SSO_BASE}/${AGORA_SSO_LOGIN_PATH}?scope=basic_info&response_type=code&state=url=${originURL}&redirect_uri=${REDIRECT_URL}&client_id=${AGORA_SSO_CLIENT_ID}`;
13
+ console.log('sso', ssoUrl);
14
+ window.location.href = ssoUrl;
15
+ };