agora-appbuilder-core 4.1.0-beta-9 → 4.1.0-beta-11

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 (26) hide show
  1. package/package.json +1 -1
  2. package/template/android/build.gradle +2 -0
  3. package/template/bridge/rtc/webNg/RtcEngine.ts +7 -7
  4. package/template/defaultConfig.js +2 -2
  5. package/template/package.json +1 -0
  6. package/template/src/ai-agent/components/AgentControls/AgentContext.tsx +55 -188
  7. package/template/src/ai-agent/components/AgentControls/index.tsx +0 -6
  8. package/template/src/ai-agent/components/AgentControls/message.ts +1003 -0
  9. package/template/src/ai-agent/components/AudioVisualizer.tsx +18 -14
  10. package/template/src/ai-agent/components/CustomSettingsPanel.tsx +5 -3
  11. package/template/src/ai-agent/components/SelectAiAgent.tsx +1 -1
  12. package/template/src/ai-agent/components/SelectAiAgentVoice.tsx +3 -3
  13. package/template/src/ai-agent/components/SelectUserLanguage.tsx +3 -2
  14. package/template/src/ai-agent/components/UserPrompt.tsx +1 -1
  15. package/template/src/ai-agent/components/agent-chat-panel/agent-chat-ui.tsx +94 -13
  16. package/template/src/ai-agent/components/mobile/MobileLayoutComponent.tsx +0 -1
  17. package/template/src/ai-agent/index.tsx +0 -7
  18. package/template/src/ai-agent/layout/ConversationalAI.tsx +0 -2
  19. package/template/src/ai-agent/layout/DefaultAIOnly.tsx +1 -7
  20. package/template/src/ai-agent/utils.ts +56 -1
  21. package/template/src/components/ChatContext.ts +5 -0
  22. package/template/src/components/StorageContext.tsx +16 -0
  23. package/template/src/logger/AppBuilderLogger.tsx +1 -1
  24. package/template/src/pages/video-call/VideoCallMobileView.tsx +5 -5
  25. package/template/src/rtm-events-api/LocalEvents.ts +1 -0
  26. package/template/src/subComponents/ChatBubble.tsx +16 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.0-beta-9",
3
+ "version": "4.1.0-beta-11",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -10,6 +10,8 @@ buildscript {
10
10
  // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
11
11
  ndkVersion = "23.1.7779620"
12
12
  kotlinVersion = "1.8.21" // added this for datadog logs - https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/setup/reactnative/#setup
13
+ androidXAnnotation = "1.2.0"
14
+ androidXBrowser = "1.3.0"
13
15
  }
14
16
  subprojects { subproject ->
15
17
  afterEvaluate{
@@ -894,13 +894,13 @@ export default class RtcEngine {
894
894
 
895
895
  /* Recieve Captions */
896
896
  this.client.on('stream-message', (uid: UID, payload: UInt8Array) => {
897
- logger.debug(
898
- LogSource.AgoraSDK,
899
- 'Event',
900
- 'RTC [stream-message](stt-web: onStreamMessageCallback)',
901
- uid,
902
- payload,
903
- );
897
+ // logger.debug(
898
+ // LogSource.AgoraSDK,
899
+ // 'Event',
900
+ // 'RTC [stream-message](stt-web: onStreamMessageCallback)',
901
+ // uid,
902
+ // payload,
903
+ // );
904
904
  (this.eventsMap.get('onStreamMessage') as callbackType)(uid, payload);
905
905
  });
906
906
 
@@ -76,8 +76,8 @@ const DefaultConfig = {
76
76
  CHAT_ORG_NAME: '',
77
77
  CHAT_APP_NAME: '',
78
78
  CHAT_URL: '',
79
- CLI_VERSION: '3.1.0-beta-9',
80
- CORE_VERSION: '4.1.0-beta-9',
79
+ CLI_VERSION: '3.1.0-beta-11',
80
+ CORE_VERSION: '4.1.0-beta-11',
81
81
  DISABLE_LANDSCAPE_MODE: false,
82
82
  STT_AUTO_START: false,
83
83
  CLOUD_RECORDING_AUTO_START: false,
@@ -112,6 +112,7 @@
112
112
  "react-tooltip": "4.5.1",
113
113
  "rn-emoji-keyboard": "^1.7.0",
114
114
  "rn-fetch-blob": "0.12.0",
115
+ "text-encoding": "^0.7.0",
115
116
  "white-web-sdk": "2.16.42"
116
117
  },
117
118
  "devDependencies": {
@@ -10,6 +10,15 @@ import {
10
10
  Toast,
11
11
  useRtc,
12
12
  } from 'customization-api';
13
+ import LocalEventEmitter, {
14
+ LocalEventsEnum,
15
+ } from '../../../../src/rtm-events-api/LocalEvents';
16
+ import {
17
+ messageService,
18
+ initializeMessageEngine,
19
+ closeMessageEngine,
20
+ IMessageListItem,
21
+ } from './message';
13
22
 
14
23
  export interface ChatItem {
15
24
  id: string;
@@ -30,8 +39,6 @@ export interface AgentContextInterface {
30
39
  setIsSubscribedForStreams: (state: boolean) => void;
31
40
  agentUID: UidType | null;
32
41
  setAgentUID: (uid: UidType | null) => void;
33
- chatItems: ChatItem[];
34
- addChatItem: (newItem: ChatItem) => void;
35
42
  agentId: string;
36
43
  setAgentId: (id: string) => void;
37
44
  agentVoice?: keyof typeof AI_AGENT_VOICE | '';
@@ -42,6 +49,9 @@ export interface AgentContextInterface {
42
49
  setPrompt: (prompt: string) => void;
43
50
  isInterruptionHandlingEnabled: boolean;
44
51
  setIsInterruptionHandlingEnabled: (value: boolean) => void;
52
+ chatHistory: IMessageListItem[];
53
+ setChatHistory: (history: IMessageListItem[]) => void;
54
+ clearChatHistory: () => void;
45
55
  }
46
56
 
47
57
  export const AgentContext = createContext<AgentContextInterface>({
@@ -56,8 +66,6 @@ export const AgentContext = createContext<AgentContextInterface>({
56
66
  setIsSubscribedForStreams: () => {},
57
67
  agentUID: null,
58
68
  setAgentUID: () => {},
59
- chatItems: [],
60
- addChatItem: () => {}, // Default no-op
61
69
  agentVoice: '',
62
70
  setAgentVoice: () => {},
63
71
  agentId: '',
@@ -68,6 +76,9 @@ export const AgentContext = createContext<AgentContextInterface>({
68
76
  setIsInterruptionHandlingEnabled: () => {},
69
77
  language: '',
70
78
  setLanguage: () => {},
79
+ chatHistory: [],
80
+ setChatHistory: () => {},
81
+ clearChatHistory: () => {},
71
82
  });
72
83
 
73
84
  /**
@@ -106,7 +117,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
106
117
  const [agentAuthToken, setAgentAuthToken] = useState<string | null>(null);
107
118
  const [agentUID, setAgentUID] = useState<UidType | null>(null);
108
119
  const [isSubscribedForStreams, setIsSubscribedForStreams] = useState(false);
109
- const [chatItems, setChatItems] = useState<ChatItem[]>([]);
120
+ const [chatHistory, setChatHistory] = useState<IMessageListItem[]>([]);
110
121
  const [agentId, setAgentId] = useState('');
111
122
  const [agentVoice, setAgentVoice] =
112
123
  useState<AgentContextInterface['agentVoice']>('');
@@ -140,117 +151,34 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
140
151
 
141
152
  React.useEffect(() => {
142
153
  if (!isSubscribedForStreams) {
143
- RtcEngineUnsafe.addListener(
144
- 'onStreamMessage',
145
- handleStreamMessageCallback,
146
- );
154
+ RtcEngineUnsafe.addListener('onStreamMessage', (...args: any[]) => {
155
+ messageService?.handleStreamMessage(args[1]);
156
+ });
147
157
  setIsSubscribedForStreams(true);
148
158
  }
149
159
  }, []);
150
160
 
151
- const handleStreamMessageCallback = (...args) => {
152
- console.log('rec', args);
153
- parseData(args[1]);
154
- };
155
-
156
- const parseData = data => {
157
- let decoder = new TextDecoder('utf-8');
158
- let decodedMessage = decoder.decode(data);
159
- console.log('[test] textstream raw data', decodedMessage);
160
- handleChunk(decodedMessage);
161
- };
162
- // Function to process received chunk via event emitter
163
- const handleChunk = (formattedChunk: string) => {
164
- try {
165
- // Split the chunk by the delimiter "|"
166
- const [message_id, partIndexStr, totalPartsStr, content] =
167
- formattedChunk.split('|');
168
-
169
- const part_index = parseInt(partIndexStr, 10);
170
- const total_parts =
171
- totalPartsStr === '???' ? -1 : parseInt(totalPartsStr, 10); // -1 means total parts unknown
172
-
173
- // Ensure total_parts is known before processing further
174
- if (total_parts === -1) {
175
- console.warn(
176
- `Total parts for message ${message_id} unknown, waiting for further parts.`,
177
- );
178
- return;
179
- }
180
-
181
- const chunkData = {
182
- message_id,
183
- part_index,
184
- total_parts,
185
- content,
186
- };
187
-
188
- // Check if we already have an entry for this message
189
- if (!messageCache[message_id]) {
190
- messageCache[message_id] = [];
191
- // Set a timeout to discard incomplete messages
192
- setTimeout(() => {
193
- if (messageCache[message_id]?.length !== total_parts) {
194
- console.warn(`Incomplete message with ID ${message_id} discarded`);
195
- delete messageCache[message_id]; // Discard incomplete message
196
- }
197
- }, TIMEOUT_MS);
198
- }
199
-
200
- // Cache this chunk by message_id
201
- messageCache[message_id].push(chunkData);
202
-
203
- // If all parts are received, reconstruct the message
204
- if (messageCache[message_id].length === total_parts) {
205
- const completeMessage = reconstructMessage(messageCache[message_id]);
206
- const data = atob(completeMessage);
207
- const {stream_id, is_final, text, text_ts} = JSON.parse(data);
208
- /** Data type of above object
209
- * stream_id: number
210
- * is_final: boolean
211
- * text: string
212
- * text_ts: number
213
- */
214
- const textItem = {
215
- id: message_id,
216
- uid: stream_id,
217
- time: text_ts,
218
- dataType: 'transcribe',
219
- text: text,
220
- isFinal: is_final,
221
- isSelf: stream_id === 0 ? false : true,
222
- };
223
-
224
- if (text.trim().length > 0) {
225
- //this.emit("textChanged", textItem);
226
- console.warn('emit textChanged: ', textItem);
227
- addChatItem(textItem);
228
- }
229
-
230
- // Clean up the cache
231
- delete messageCache[message_id];
161
+ React.useEffect(() => {
162
+ const getChatHistoryFromEvent = (event: MessageEvent) => {
163
+ const {data} = event;
164
+ // console.log('get chat history from event', data);
165
+ if (data.type === 'message') {
166
+ setChatHistory(prevChatHistory => [...(data?.chatHistory || [])]);
232
167
  }
233
- } catch (error) {
234
- console.error('Error processing chunk:', error);
235
- }
236
- };
237
-
238
- const reconstructMessage = chunks => {
239
- // Sort chunks by their part index
240
- chunks.sort((a, b) => a.part_index - b.part_index);
241
-
242
- // Concatenate all chunks to form the full message
243
- return chunks.map(chunk => chunk.content).join('');
244
- };
245
-
246
- useEffect(() => {
247
- console.log(
248
- 'debugging users agent contrl',
249
- agentConnectionState,
250
- {users},
251
- agentUID,
168
+ };
169
+ LocalEventEmitter.on(
170
+ LocalEventsEnum.AGENT_TRANSCRIPT_CHANGE,
171
+ getChatHistoryFromEvent,
252
172
  );
173
+ return () => {
174
+ LocalEventEmitter.off(
175
+ LocalEventsEnum.AGENT_TRANSCRIPT_CHANGE,
176
+ getChatHistoryFromEvent,
177
+ );
178
+ };
179
+ }, []);
253
180
 
181
+ useEffect(() => {
254
182
  // welcome agent
255
183
  const aiAgentUID = users.filter(item => item === agentUID);
256
184
 
@@ -266,6 +194,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
266
194
  agentConnectionState !== AgentState.AGENT_CONNECTED)
267
195
  ) {
268
196
  setAgentConnectionState(AgentState.AGENT_CONNECTED);
197
+ initializeMessageEngine();
269
198
  if (isStartAPICalled) {
270
199
  setStartAPICalled(false);
271
200
  }
@@ -294,6 +223,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
294
223
  !aiAgentUID.length &&
295
224
  agentConnectionState !== AgentState.NOT_CONNECTED)
296
225
  ) {
226
+ closeMessageEngine(); // release message engine
297
227
  setAgentConnectionState(AgentState.NOT_CONNECTED);
298
228
  if (isStopAPICalled) {
299
229
  setStartAPICalled(true);
@@ -307,6 +237,10 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
307
237
  isStopAPICalled,
308
238
  ]);
309
239
 
240
+ const clearChatHistory = () => {
241
+ setChatHistory([]);
242
+ };
243
+
310
244
  const handleConnectionToggle = async (forceStop: boolean = false) => {
311
245
  try {
312
246
  // connect to agent when agent is in not connected state or when earlier connect failed
@@ -318,19 +252,20 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
318
252
  try {
319
253
  setAgentConnectionState(AgentState.REQUEST_SENT);
320
254
  setStartAPICalled(true);
255
+ const params = {
256
+ agent_id: agentId,
257
+ prompt: prompt,
258
+ voice: agents?.find(a => a.id === agentId)?.config?.tts?.params
259
+ ?.voice_name,
260
+ enable_interruption_handling: isInterruptionHandlingEnabled,
261
+ language: language,
262
+ };
321
263
  const data = await connectToAIAgent(
322
264
  'start',
323
265
  channel_name,
324
266
  localUid,
325
267
  store.token,
326
- {
327
- agent_id: agentId || agents?.length ? agents[0].id : null,
328
- prompt: prompt,
329
- voice: agents.find(a => a.id === agentId)?.config?.tts?.params
330
- ?.voice_name,
331
- enable_interruption_handling: isInterruptionHandlingEnabled,
332
- language: language,
333
- },
268
+ params,
334
269
  );
335
270
  // console.log("response X-Client-ID", newClientId, typeof newClientId)
336
271
  // @ts-ignore
@@ -444,75 +379,6 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
444
379
  }
445
380
  };
446
381
 
447
- /**
448
- * Adds a new chat item to the chat state while ensuring:
449
- * - Outdated messages are discarded.
450
- * - Non-finalized messages are updated if a newer message is received.
451
- * - Finalized messages are added without duplication.
452
- * - Chat items remain sorted by their `time` property.
453
- *
454
- * @param newItem The new chat item to add.
455
- */
456
- const addChatItem = (newItem: ChatItem) => {
457
- setChatItems(prevItems => {
458
- // Find the index of the last finalized chat item for the same user
459
- // Finalized messages are typically considered "complete" and should not be updated by non-final messages
460
- const LastFinalIndex = prevItems.findLastIndex(
461
- el => el.uid === newItem.uid && el.isFinal,
462
- );
463
-
464
- // Find the index of the last non-finalized chat item for the same user
465
- // Non-finalized messages represent "in-progress" messages that can be updated or replaced
466
- const LastNonFinalIndex = prevItems.findLastIndex(
467
- el => el.uid === newItem.uid && !el.isFinal,
468
- );
469
-
470
- // Retrieve the actual items for the indices found above
471
- const LastFinalItem =
472
- LastFinalIndex !== -1 ? prevItems[LastFinalIndex] : null;
473
- const LastNonFinalItem =
474
- LastNonFinalIndex !== -1 ? prevItems[LastNonFinalIndex] : null;
475
-
476
- // If the new message's timestamp is older than or equal to the last finalized message,
477
- // it is considered outdated and discarded to prevent unnecessary overwrites.
478
- if (LastFinalItem && newItem.time <= LastFinalItem.time) {
479
- console.log(
480
- '[AgentProvider] addChatItem - Discarded outdated message:',
481
- newItem,
482
- );
483
- return prevItems; // Return the previous state without changes
484
- }
485
-
486
- // Create a new copy of the current chat items to maintain immutability
487
- let updatedItems = [...prevItems];
488
-
489
- // If there is a non-finalized message for the same user, replace it with the new message
490
- if (LastNonFinalItem) {
491
- console.log(
492
- '[AgentProvider] addChatItem - Updating non-finalized message:',
493
- newItem,
494
- );
495
- updatedItems[LastNonFinalIndex] = newItem; // Replace the non-finalized message
496
- } else {
497
- // If no non-finalized message exists, the new message is added to the array
498
- console.log(
499
- '[AgentProvider] addChatItem - Adding new message:',
500
- newItem,
501
- );
502
-
503
- // Use binary search to find the correct insertion index for the new message
504
- // This ensures the array remains sorted by the `time` property
505
- const insertIndex = findInsertionIndex(updatedItems, newItem.time);
506
-
507
- // Insert the new message at the correct position to maintain chronological order
508
- updatedItems.splice(insertIndex, 0, newItem);
509
- }
510
-
511
- // Return the updated array, which will replace the previous state
512
- return updatedItems;
513
- });
514
- };
515
-
516
382
  const value = {
517
383
  toggleAgentConnection: handleConnectionToggle,
518
384
  agentConnectionState,
@@ -523,8 +389,6 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
523
389
  setIsSubscribedForStreams,
524
390
  agentUID,
525
391
  setAgentUID,
526
- chatItems,
527
- addChatItem, // Expose the function in the context
528
392
  agentId,
529
393
  setAgentId,
530
394
  agentVoice,
@@ -535,6 +399,9 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
535
399
  setIsInterruptionHandlingEnabled,
536
400
  language,
537
401
  setLanguage,
402
+ setChatHistory,
403
+ chatHistory,
404
+ clearChatHistory,
538
405
  };
539
406
 
540
407
  return (
@@ -18,12 +18,6 @@ export const AgentControl: React.FC = () => {
18
18
  const isAwaitingLeave = agentConnectionState === AgentState.AWAITING_LEAVE;
19
19
  const isAgentAvailable = useIsAgentAvailable();
20
20
 
21
- console.log(
22
- 'Agent Control--',
23
- {agentConnectionState},
24
- {bth: AI_AGENT_STATE[agentConnectionState]},
25
- );
26
-
27
21
  const isLoading =
28
22
  agentConnectionState === AgentState.REQUEST_SENT ||
29
23
  agentConnectionState === AgentState.AGENT_DISCONNECT_REQUEST ||