agora-appbuilder-core 4.1.0-beta-4 → 4.1.0-beta-5
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/src/ai-agent/components/AgentControls/AgentConnectionWrapper.tsx +120 -1
- package/template/src/ai-agent/components/AgentControls/index.tsx +1 -1
- package/template/src/ai-agent/components/CustomChatPanel.tsx +1 -115
- package/template/src/ai-agent/components/CustomCreate.tsx +1 -1
- package/template/src/ai-agent/components/UserPrompt.tsx +14 -3
- package/template/src/ai-agent/components/agent-chat-panel/agent-chat-ui.tsx +1 -3
- package/template/src/components/room-info/useRoomInfo.tsx +1 -0
- package/template/src/utils/useJoinRoom.ts +2 -0
package/package.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
UidType,
|
|
3
|
+
useContent,
|
|
4
|
+
useRoomInfo,
|
|
5
|
+
Toast,
|
|
6
|
+
useRtc,
|
|
7
|
+
} from 'customization-api';
|
|
2
8
|
import React, {createContext, useContext, useEffect} from 'react';
|
|
3
9
|
import {AgentContext} from './AgentContext';
|
|
4
10
|
import {AgentState} from './const';
|
|
@@ -25,12 +31,125 @@ export const AgentConnectionProvider: React.FC<{children: React.ReactNode}> = ({
|
|
|
25
31
|
agentId,
|
|
26
32
|
setAgentUID,
|
|
27
33
|
prompt,
|
|
34
|
+
isSubscribedForStreams,
|
|
35
|
+
setIsSubscribedForStreams,
|
|
36
|
+
addChatItem,
|
|
28
37
|
} = useContext(AgentContext);
|
|
29
38
|
const {
|
|
30
39
|
data: {channel: channel_name, uid: localUid, agents},
|
|
31
40
|
} = useRoomInfo();
|
|
32
41
|
const {store} = useContext(StorageContext);
|
|
33
42
|
|
|
43
|
+
const {RtcEngineUnsafe} = useRtc();
|
|
44
|
+
|
|
45
|
+
const messageCache = {};
|
|
46
|
+
const TIMEOUT_MS = 5000; // Timeout for incomplete messages
|
|
47
|
+
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
if (!isSubscribedForStreams) {
|
|
50
|
+
RtcEngineUnsafe.addListener(
|
|
51
|
+
'onStreamMessage',
|
|
52
|
+
handleStreamMessageCallback,
|
|
53
|
+
);
|
|
54
|
+
setIsSubscribedForStreams(true);
|
|
55
|
+
}
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const handleStreamMessageCallback = (...args) => {
|
|
59
|
+
console.log('rec', args);
|
|
60
|
+
parseData(args[1]);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const parseData = data => {
|
|
64
|
+
let decoder = new TextDecoder('utf-8');
|
|
65
|
+
let decodedMessage = decoder.decode(data);
|
|
66
|
+
console.log('[test] textstream raw data', decodedMessage);
|
|
67
|
+
handleChunk(decodedMessage);
|
|
68
|
+
};
|
|
69
|
+
// Function to process received chunk via event emitter
|
|
70
|
+
const handleChunk = (formattedChunk: string) => {
|
|
71
|
+
try {
|
|
72
|
+
// Split the chunk by the delimiter "|"
|
|
73
|
+
const [message_id, partIndexStr, totalPartsStr, content] =
|
|
74
|
+
formattedChunk.split('|');
|
|
75
|
+
|
|
76
|
+
const part_index = parseInt(partIndexStr, 10);
|
|
77
|
+
const total_parts =
|
|
78
|
+
totalPartsStr === '???' ? -1 : parseInt(totalPartsStr, 10); // -1 means total parts unknown
|
|
79
|
+
|
|
80
|
+
// Ensure total_parts is known before processing further
|
|
81
|
+
if (total_parts === -1) {
|
|
82
|
+
console.warn(
|
|
83
|
+
`Total parts for message ${message_id} unknown, waiting for further parts.`,
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const chunkData = {
|
|
89
|
+
message_id,
|
|
90
|
+
part_index,
|
|
91
|
+
total_parts,
|
|
92
|
+
content,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Check if we already have an entry for this message
|
|
96
|
+
if (!messageCache[message_id]) {
|
|
97
|
+
messageCache[message_id] = [];
|
|
98
|
+
// Set a timeout to discard incomplete messages
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
if (messageCache[message_id]?.length !== total_parts) {
|
|
101
|
+
console.warn(`Incomplete message with ID ${message_id} discarded`);
|
|
102
|
+
delete messageCache[message_id]; // Discard incomplete message
|
|
103
|
+
}
|
|
104
|
+
}, TIMEOUT_MS);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Cache this chunk by message_id
|
|
108
|
+
messageCache[message_id].push(chunkData);
|
|
109
|
+
|
|
110
|
+
// If all parts are received, reconstruct the message
|
|
111
|
+
if (messageCache[message_id].length === total_parts) {
|
|
112
|
+
const completeMessage = reconstructMessage(messageCache[message_id]);
|
|
113
|
+
const data = atob(completeMessage);
|
|
114
|
+
const {stream_id, is_final, text, text_ts} = JSON.parse(data);
|
|
115
|
+
/** Data type of above object
|
|
116
|
+
* stream_id: number
|
|
117
|
+
* is_final: boolean
|
|
118
|
+
* text: string
|
|
119
|
+
* text_ts: number
|
|
120
|
+
*/
|
|
121
|
+
const textItem = {
|
|
122
|
+
id: message_id,
|
|
123
|
+
uid: stream_id,
|
|
124
|
+
time: text_ts,
|
|
125
|
+
dataType: 'transcribe',
|
|
126
|
+
text: text,
|
|
127
|
+
isFinal: is_final,
|
|
128
|
+
isSelf: stream_id === 0 ? false : true,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
if (text.trim().length > 0) {
|
|
132
|
+
//this.emit("textChanged", textItem);
|
|
133
|
+
console.warn('emit textChanged: ', textItem);
|
|
134
|
+
addChatItem(textItem);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Clean up the cache
|
|
138
|
+
delete messageCache[message_id];
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Error processing chunk:', error);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const reconstructMessage = chunks => {
|
|
146
|
+
// Sort chunks by their part index
|
|
147
|
+
chunks.sort((a, b) => a.part_index - b.part_index);
|
|
148
|
+
|
|
149
|
+
// Concatenate all chunks to form the full message
|
|
150
|
+
return chunks.map(chunk => chunk.content).join('');
|
|
151
|
+
};
|
|
152
|
+
|
|
34
153
|
useEffect(() => {
|
|
35
154
|
console.log('debugging users agent contrl', {users});
|
|
36
155
|
// welcome agent
|
|
@@ -1,122 +1,8 @@
|
|
|
1
1
|
import {StyleSheet, View} from 'react-native';
|
|
2
|
-
import React
|
|
3
|
-
import {useRtc} from 'customization-api';
|
|
4
|
-
import {AgentContext} from './AgentControls/AgentContext';
|
|
2
|
+
import React from 'react';
|
|
5
3
|
import ChatScreen from './agent-chat-panel/agent-chat-ui';
|
|
6
4
|
|
|
7
5
|
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
6
|
return (
|
|
121
7
|
<View style={styles.container}>
|
|
122
8
|
<ChatScreen />
|
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
import React, {useContext} from 'react';
|
|
1
|
+
import React, {useContext, useEffect} from 'react';
|
|
2
2
|
import {View, TextInput, StyleSheet, Text, Platform} from 'react-native';
|
|
3
3
|
import ThemeConfig from '../../theme';
|
|
4
4
|
import {AgentContext} from './AgentControls/AgentContext';
|
|
5
|
+
import {useRoomInfo} from 'customization-api';
|
|
5
6
|
|
|
6
7
|
const UserPrompt = () => {
|
|
7
|
-
const {prompt, setPrompt, agentConnectionState} =
|
|
8
|
+
const {prompt, setPrompt, agentConnectionState, agentId} =
|
|
9
|
+
useContext(AgentContext);
|
|
10
|
+
const {
|
|
11
|
+
data: {agents},
|
|
12
|
+
} = useRoomInfo();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (agentId) {
|
|
16
|
+
setPrompt(agents?.find(a => a?.id === agentId)?.config?.llm?.prompt);
|
|
17
|
+
}
|
|
18
|
+
}, [agentId, agents, setPrompt]);
|
|
8
19
|
return (
|
|
9
20
|
<>
|
|
10
21
|
<Text style={styles.label}>Prompt</Text>
|
|
@@ -20,7 +31,7 @@ const UserPrompt = () => {
|
|
|
20
31
|
value={prompt}
|
|
21
32
|
onChangeText={setPrompt}
|
|
22
33
|
placeholder="Customize Prompt"
|
|
23
|
-
numberOfLines={
|
|
34
|
+
numberOfLines={10}
|
|
24
35
|
multiline={true}
|
|
25
36
|
/>
|
|
26
37
|
</View>
|
|
@@ -52,6 +52,7 @@ const JOIN_CHANNEL_PHRASE_AND_GET_USER = gql`
|
|
|
52
52
|
llm {
|
|
53
53
|
agent_name
|
|
54
54
|
model
|
|
55
|
+
prompt
|
|
55
56
|
}
|
|
56
57
|
tts {
|
|
57
58
|
vendor
|
|
@@ -115,6 +116,7 @@ const JOIN_CHANNEL_PHRASE = gql`
|
|
|
115
116
|
llm {
|
|
116
117
|
agent_name
|
|
117
118
|
model
|
|
119
|
+
prompt
|
|
118
120
|
}
|
|
119
121
|
tts {
|
|
120
122
|
vendor
|