agora-appbuilder-core 4.1.0-beta-8 → 4.1.0-beta-10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.0-beta-8",
3
+ "version": "4.1.0-beta-10",
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{
@@ -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-8',
80
- CORE_VERSION: '4.1.0-beta-8',
79
+ CLI_VERSION: '3.1.0-beta-10',
80
+ CORE_VERSION: '4.1.0-beta-10',
81
81
  DISABLE_LANDSCAPE_MODE: false,
82
82
  STT_AUTO_START: false,
83
83
  CLOUD_RECORDING_AUTO_START: false,
@@ -85,6 +85,7 @@ const DefaultConfig = {
85
85
  AUTO_CONNECT_RTM: false,
86
86
  ENABLE_CONVERSATIONAL_AI: false,
87
87
  CUSTOMIZE_AGENT: true,
88
+ AI_LAYOUT: 'DEFAULT_LAYOUT',
88
89
  };
89
90
 
90
91
  module.exports = DefaultConfig;
@@ -172,6 +172,7 @@ interface ConfigInterface {
172
172
  AUTO_CONNECT_RTM: boolean;
173
173
  ENABLE_CONVERSATIONAL_AI: boolean;
174
174
  CUSTOMIZE_AGENT: boolean;
175
+ AI_LAYOUT: string;
175
176
  }
176
177
  declare var $config: ConfigInterface;
177
178
  declare module 'customization' {
@@ -57,7 +57,6 @@
57
57
  "@react-native-async-storage/async-storage": "1.19.2",
58
58
  "@react-native-community/checkbox": "0.5.16",
59
59
  "@react-native-community/clipboard": "1.5.1",
60
- "@splinetool/react-spline": "^4.0.0",
61
60
  "@splinetool/runtime": "^1.9.69",
62
61
  "@supersami/rn-foreground-service": "^1.1.1",
63
62
  "add": "^2.0.6",
@@ -66,7 +65,7 @@
66
65
  "agora-extension-beauty-effect": "^1.0.2-beta",
67
66
  "agora-extension-virtual-background": "^1.1.3",
68
67
  "agora-react-native-rtm": "1.5.1",
69
- "agora-rtc-sdk-ng": "4.23.0",
68
+ "agora-rtc-sdk-ng": "4.23.2",
70
69
  "agora-rtm-sdk": "1.5.1",
71
70
  "buffer": "^6.0.3",
72
71
  "electron-log": "4.3.5",
@@ -113,6 +112,7 @@
113
112
  "react-tooltip": "4.5.1",
114
113
  "rn-emoji-keyboard": "^1.7.0",
115
114
  "rn-fetch-blob": "0.12.0",
115
+ "text-encoding": "^0.7.0",
116
116
  "white-web-sdk": "2.16.42"
117
117
  },
118
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,108 +151,32 @@ 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
- };
168
+ };
169
+ LocalEventEmitter.on(
170
+ LocalEventsEnum.AGENT_TRANSCRIPT_CHANGE,
171
+ getChatHistoryFromEvent,
172
+ );
173
+ return () => {
174
+ LocalEventEmitter.off(
175
+ LocalEventsEnum.AGENT_TRANSCRIPT_CHANGE,
176
+ getChatHistoryFromEvent,
177
+ );
178
+ };
179
+ }, []);
245
180
 
246
181
  useEffect(() => {
247
182
  console.log(
@@ -266,6 +201,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
266
201
  agentConnectionState !== AgentState.AGENT_CONNECTED)
267
202
  ) {
268
203
  setAgentConnectionState(AgentState.AGENT_CONNECTED);
204
+ initializeMessageEngine();
269
205
  if (isStartAPICalled) {
270
206
  setStartAPICalled(false);
271
207
  }
@@ -294,6 +230,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
294
230
  !aiAgentUID.length &&
295
231
  agentConnectionState !== AgentState.NOT_CONNECTED)
296
232
  ) {
233
+ closeMessageEngine(); // release message engine
297
234
  setAgentConnectionState(AgentState.NOT_CONNECTED);
298
235
  if (isStopAPICalled) {
299
236
  setStartAPICalled(true);
@@ -307,6 +244,10 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
307
244
  isStopAPICalled,
308
245
  ]);
309
246
 
247
+ const clearChatHistory = () => {
248
+ setChatHistory([]);
249
+ };
250
+
310
251
  const handleConnectionToggle = async (forceStop: boolean = false) => {
311
252
  try {
312
253
  // connect to agent when agent is in not connected state or when earlier connect failed
@@ -324,7 +265,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
324
265
  localUid,
325
266
  store.token,
326
267
  {
327
- agent_id: agentId,
268
+ agent_id: agentId || agents?.length ? agents[0].id : null,
328
269
  prompt: prompt,
329
270
  voice: agents.find(a => a.id === agentId)?.config?.tts?.params
330
271
  ?.voice_name,
@@ -400,7 +341,7 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
400
341
  setAgentConnectionState(AgentState.AGENT_DISCONNECT_REQUEST);
401
342
  setStopAPICalled(true);
402
343
  await connectToAIAgent('stop', channel_name, localUid, store.token, {
403
- agent_id: agentId,
344
+ agent_id: agentId || agents?.length ? agents[0].id : null,
404
345
  });
405
346
  setStore(prevState => {
406
347
  return {
@@ -444,75 +385,6 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
444
385
  }
445
386
  };
446
387
 
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
388
  const value = {
517
389
  toggleAgentConnection: handleConnectionToggle,
518
390
  agentConnectionState,
@@ -523,8 +395,6 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
523
395
  setIsSubscribedForStreams,
524
396
  agentUID,
525
397
  setAgentUID,
526
- chatItems,
527
- addChatItem, // Expose the function in the context
528
398
  agentId,
529
399
  setAgentId,
530
400
  agentVoice,
@@ -535,6 +405,9 @@ export const AgentProvider: React.FC<{children: React.ReactNode}> = ({
535
405
  setIsInterruptionHandlingEnabled,
536
406
  language,
537
407
  setLanguage,
408
+ setChatHistory,
409
+ chatHistory,
410
+ clearChatHistory,
538
411
  };
539
412
 
540
413
  return (