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 +1 -1
- package/template/android/build.gradle +2 -0
- package/template/defaultConfig.js +3 -2
- package/template/global.d.ts +1 -0
- package/template/package.json +2 -2
- package/template/src/ai-agent/components/AgentControls/AgentContext.tsx +48 -175
- package/template/src/ai-agent/components/AgentControls/message.ts +1007 -0
- package/template/src/ai-agent/components/AudioVisualizer.tsx +18 -14
- package/template/src/ai-agent/components/CustomSettingsPanel.tsx +3 -1
- package/template/src/ai-agent/components/agent-chat-panel/agent-chat-ui.tsx +94 -13
- package/template/src/ai-agent/components/mobile/MobileLayoutComponent.tsx +1 -1
- package/template/src/ai-agent/index.tsx +20 -22
- package/template/src/ai-agent/layout/ConversationalAI.native.tsx +4 -0
- package/template/src/ai-agent/layout/ConversationalAI.tsx +23 -9
- package/template/src/ai-agent/layout/DefaultAIOnly.tsx +1 -1
- package/template/src/ai-agent/utils.ts +56 -1
- package/template/src/atoms/Dropdown.tsx +1 -1
- package/template/src/components/ChatContext.ts +5 -0
- package/template/src/logger/AppBuilderLogger.tsx +1 -1
- package/template/src/pages/video-call/VideoCallMobileView.tsx +5 -5
- package/template/src/rtm-events-api/LocalEvents.ts +1 -0
- package/template/src/subComponents/ChatBubble.tsx +16 -1
package/package.json
CHANGED
|
@@ -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-
|
|
80
|
-
CORE_VERSION: '4.1.0-beta-
|
|
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;
|
package/template/global.d.ts
CHANGED
package/template/package.json
CHANGED
|
@@ -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.
|
|
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 [
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
);
|
|
154
|
+
RtcEngineUnsafe.addListener('onStreamMessage', (...args: any[]) => {
|
|
155
|
+
messageService?.handleStreamMessage(args[1]);
|
|
156
|
+
});
|
|
147
157
|
setIsSubscribedForStreams(true);
|
|
148
158
|
}
|
|
149
159
|
}, []);
|
|
150
160
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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 (
|