@base44/superagent-native 0.0.1 → 0.0.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.
- package/LICENSE +21 -0
- package/README.md +12 -20
- package/lib/commonjs/AgentSettingsPanel.js +32 -15
- package/lib/commonjs/AgentSettingsPanel.js.map +1 -1
- package/lib/commonjs/AttachmentPickerStatusModal.js +2 -2
- package/lib/commonjs/AttachmentPickerStatusModal.js.map +1 -1
- package/lib/commonjs/ConversationChat.js +27 -11
- package/lib/commonjs/ConversationChat.js.map +1 -1
- package/lib/commonjs/ConversationComposer.js +10 -6
- package/lib/commonjs/ConversationComposer.js.map +1 -1
- package/lib/commonjs/ConversationScreen.js +2 -0
- package/lib/commonjs/ConversationScreen.js.map +1 -1
- package/lib/commonjs/MarkdownText.js +1 -1
- package/lib/commonjs/MarkdownText.js.map +1 -1
- package/lib/commonjs/MessageActionBar.js +10 -3
- package/lib/commonjs/MessageActionBar.js.map +1 -1
- package/lib/commonjs/SuperagentHomeScreen.js +17 -3
- package/lib/commonjs/SuperagentHomeScreen.js.map +1 -1
- package/lib/commonjs/ToolApprovalCard.js +1 -1
- package/lib/commonjs/ToolApprovalCard.js.map +1 -1
- package/lib/commonjs/ToolCallSummary.js +5 -1
- package/lib/commonjs/ToolCallSummary.js.map +1 -1
- package/lib/commonjs/attachmentUpload.js +2 -1
- package/lib/commonjs/attachmentUpload.js.map +1 -1
- package/lib/commonjs/conversationRuntime.js +37 -2
- package/lib/commonjs/conversationRuntime.js.map +1 -1
- package/lib/commonjs/fileTreeUtils.js +7 -0
- package/lib/commonjs/fileTreeUtils.js.map +1 -1
- package/lib/commonjs/screenParts.js +3 -3
- package/lib/commonjs/styles.js +43 -43
- package/lib/commonjs/useSuperagentConversation.js +117 -34
- package/lib/commonjs/useSuperagentConversation.js.map +1 -1
- package/lib/commonjs/useSuperagentRuntime.js +79 -24
- package/lib/commonjs/useSuperagentRuntime.js.map +1 -1
- package/lib/module/AgentSettingsPanel.js +32 -15
- package/lib/module/AgentSettingsPanel.js.map +1 -1
- package/lib/module/AttachmentPickerStatusModal.js +2 -2
- package/lib/module/AttachmentPickerStatusModal.js.map +1 -1
- package/lib/module/ConversationChat.js +27 -11
- package/lib/module/ConversationChat.js.map +1 -1
- package/lib/module/ConversationComposer.js +10 -6
- package/lib/module/ConversationComposer.js.map +1 -1
- package/lib/module/ConversationScreen.js +2 -0
- package/lib/module/ConversationScreen.js.map +1 -1
- package/lib/module/MarkdownText.js +1 -1
- package/lib/module/MarkdownText.js.map +1 -1
- package/lib/module/MessageActionBar.js +10 -3
- package/lib/module/MessageActionBar.js.map +1 -1
- package/lib/module/SuperagentHomeScreen.js +18 -4
- package/lib/module/SuperagentHomeScreen.js.map +1 -1
- package/lib/module/ToolApprovalCard.js +1 -1
- package/lib/module/ToolApprovalCard.js.map +1 -1
- package/lib/module/ToolCallSummary.js +5 -1
- package/lib/module/ToolCallSummary.js.map +1 -1
- package/lib/module/attachmentUpload.js +2 -1
- package/lib/module/attachmentUpload.js.map +1 -1
- package/lib/module/conversationRuntime.js +36 -2
- package/lib/module/conversationRuntime.js.map +1 -1
- package/lib/module/fileTreeUtils.js +6 -0
- package/lib/module/fileTreeUtils.js.map +1 -1
- package/lib/module/screenParts.js +3 -3
- package/lib/module/styles.js +43 -43
- package/lib/module/useSuperagentConversation.js +118 -35
- package/lib/module/useSuperagentConversation.js.map +1 -1
- package/lib/module/useSuperagentRuntime.js +80 -25
- package/lib/module/useSuperagentRuntime.js.map +1 -1
- package/lib/typescript/commonjs/AgentSettingsPanel.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationChat.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationComposer.d.ts.map +1 -1
- package/lib/typescript/commonjs/ConversationScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ConversationScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/SuperagentHomeScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/conversationRuntime.d.ts +3 -2
- package/lib/typescript/commonjs/conversationRuntime.d.ts.map +1 -1
- package/lib/typescript/commonjs/fileTreeUtils.d.ts +1 -0
- package/lib/typescript/commonjs/fileTreeUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +1 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/useSuperagentConversation.d.ts.map +1 -1
- package/lib/typescript/commonjs/useSuperagentRuntime.d.ts +3 -1
- package/lib/typescript/commonjs/useSuperagentRuntime.d.ts.map +1 -1
- package/lib/typescript/module/AgentSettingsPanel.d.ts.map +1 -1
- package/lib/typescript/module/ConversationChat.d.ts.map +1 -1
- package/lib/typescript/module/ConversationComposer.d.ts.map +1 -1
- package/lib/typescript/module/ConversationScreen.d.ts +1 -1
- package/lib/typescript/module/ConversationScreen.d.ts.map +1 -1
- package/lib/typescript/module/SuperagentHomeScreen.d.ts.map +1 -1
- package/lib/typescript/module/conversationRuntime.d.ts +3 -2
- package/lib/typescript/module/conversationRuntime.d.ts.map +1 -1
- package/lib/typescript/module/fileTreeUtils.d.ts +1 -0
- package/lib/typescript/module/fileTreeUtils.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +1 -0
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/useSuperagentConversation.d.ts.map +1 -1
- package/lib/typescript/module/useSuperagentRuntime.d.ts +3 -1
- package/lib/typescript/module/useSuperagentRuntime.d.ts.map +1 -1
- package/package.json +13 -11
- package/src/AgentSettingsPanel.tsx +28 -9
- package/src/AttachmentPickerStatusModal.tsx +2 -2
- package/src/ConversationChat.tsx +37 -9
- package/src/ConversationComposer.tsx +11 -6
- package/src/ConversationScreen.tsx +2 -0
- package/src/MarkdownText.tsx +1 -1
- package/src/MessageActionBar.tsx +9 -3
- package/src/SuperagentHomeScreen.tsx +18 -3
- package/src/ToolApprovalCard.tsx +1 -1
- package/src/ToolCallSummary.tsx +4 -1
- package/src/attachmentUpload.ts +2 -1
- package/src/conversationRuntime.ts +48 -4
- package/src/fileTreeUtils.ts +13 -0
- package/src/screenParts.tsx +3 -3
- package/src/styles.ts +43 -43
- package/src/types.ts +1 -0
- package/src/useSuperagentConversation.ts +116 -31
- package/src/useSuperagentRuntime.ts +80 -24
package/src/styles.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { StyleSheet } from 'react-native';
|
|
2
2
|
|
|
3
3
|
export const styles = StyleSheet.create({
|
|
4
|
-
safeArea: { flex: 1, backgroundColor: '#
|
|
4
|
+
safeArea: { flex: 1, backgroundColor: '#F7F7F8' },
|
|
5
5
|
container: { paddingBottom: 34, paddingHorizontal: 14, paddingTop: 10 },
|
|
6
6
|
homeTopBar: {
|
|
7
7
|
alignItems: 'center',
|
|
8
|
-
borderBottomColor: '#
|
|
8
|
+
borderBottomColor: '#E4E4E7',
|
|
9
9
|
borderBottomWidth: 1,
|
|
10
10
|
flexDirection: 'row',
|
|
11
11
|
minHeight: 62,
|
|
@@ -24,7 +24,7 @@ export const styles = StyleSheet.create({
|
|
|
24
24
|
justifyContent: 'center',
|
|
25
25
|
minWidth: 0,
|
|
26
26
|
},
|
|
27
|
-
topBarTitle: { color: '#
|
|
27
|
+
topBarTitle: { color: '#18181B', fontSize: 17, fontWeight: '900', letterSpacing: 0, lineHeight: 21 },
|
|
28
28
|
homeIntro: { marginBottom: 14, paddingTop: 2 },
|
|
29
29
|
header: {
|
|
30
30
|
alignItems: 'center',
|
|
@@ -35,13 +35,13 @@ export const styles = StyleSheet.create({
|
|
|
35
35
|
paddingTop: 4,
|
|
36
36
|
},
|
|
37
37
|
headerAccessory: { flexShrink: 0, marginLeft: 12 },
|
|
38
|
-
eyebrow: { color: '#
|
|
39
|
-
title: { color: '#
|
|
40
|
-
subtitle: { color: '#
|
|
38
|
+
eyebrow: { color: '#71717A', fontSize: 12, fontWeight: '800', marginTop: 2 },
|
|
39
|
+
title: { color: '#18181B', fontSize: 24, fontWeight: '900', letterSpacing: 0, lineHeight: 29 },
|
|
40
|
+
subtitle: { color: '#52525B', fontSize: 14, fontWeight: '600', lineHeight: 20, marginTop: 5 },
|
|
41
41
|
headerAvatar: {
|
|
42
42
|
alignItems: 'center',
|
|
43
|
-
backgroundColor: '#
|
|
44
|
-
borderColor: '#
|
|
43
|
+
backgroundColor: '#FFFFFF',
|
|
44
|
+
borderColor: '#E4E4E7',
|
|
45
45
|
borderRadius: 22,
|
|
46
46
|
borderWidth: 1,
|
|
47
47
|
height: 44,
|
|
@@ -50,15 +50,15 @@ export const styles = StyleSheet.create({
|
|
|
50
50
|
},
|
|
51
51
|
loadingPanel: {
|
|
52
52
|
alignItems: 'center',
|
|
53
|
-
backgroundColor: '#
|
|
54
|
-
borderColor: '#
|
|
53
|
+
backgroundColor: '#FFFFFF',
|
|
54
|
+
borderColor: '#E4E4E7',
|
|
55
55
|
borderRadius: 18,
|
|
56
56
|
borderWidth: 1,
|
|
57
57
|
padding: 32,
|
|
58
58
|
},
|
|
59
59
|
heroPanel: {
|
|
60
|
-
backgroundColor: '#
|
|
61
|
-
borderColor: '#
|
|
60
|
+
backgroundColor: '#FFFFFF',
|
|
61
|
+
borderColor: '#E4E4E7',
|
|
62
62
|
borderRadius: 22,
|
|
63
63
|
borderWidth: 1,
|
|
64
64
|
overflow: 'hidden',
|
|
@@ -70,7 +70,7 @@ export const styles = StyleSheet.create({
|
|
|
70
70
|
justifyContent: 'space-between',
|
|
71
71
|
marginBottom: 16,
|
|
72
72
|
},
|
|
73
|
-
heroLabel: { color: '#
|
|
73
|
+
heroLabel: { color: '#71717A', fontSize: 11, fontWeight: '900', textTransform: 'uppercase' },
|
|
74
74
|
agentCard: {
|
|
75
75
|
alignItems: 'flex-start',
|
|
76
76
|
flexDirection: 'row',
|
|
@@ -78,23 +78,23 @@ export const styles = StyleSheet.create({
|
|
|
78
78
|
},
|
|
79
79
|
avatar: {
|
|
80
80
|
alignItems: 'center',
|
|
81
|
-
backgroundColor: '#
|
|
82
|
-
borderColor: '#
|
|
81
|
+
backgroundColor: '#F4F4F5',
|
|
82
|
+
borderColor: '#E4E4E7',
|
|
83
83
|
borderRadius: 24,
|
|
84
84
|
borderWidth: 1,
|
|
85
85
|
height: 52,
|
|
86
86
|
justifyContent: 'center',
|
|
87
87
|
width: 52,
|
|
88
88
|
},
|
|
89
|
-
avatarText: { color: '#
|
|
89
|
+
avatarText: { color: '#18181B', fontSize: 18, fontWeight: '900' },
|
|
90
90
|
agentCardBody: { flex: 1, marginLeft: 13, minWidth: 0 },
|
|
91
|
-
agentName: { color: '#
|
|
92
|
-
agentDescription: { color: '#
|
|
93
|
-
chevron: { color: '#
|
|
91
|
+
agentName: { color: '#18181B', fontSize: 21, fontWeight: '900', lineHeight: 25 },
|
|
92
|
+
agentDescription: { color: '#52525B', fontSize: 14, fontWeight: '600', lineHeight: 20, marginTop: 7 },
|
|
93
|
+
chevron: { color: '#A1A1AA', fontSize: 20, fontWeight: '900' },
|
|
94
94
|
agentMetaRow: { flexDirection: 'row', flexWrap: 'wrap', marginLeft: 65, marginTop: 14 },
|
|
95
95
|
agentMetaPill: {
|
|
96
|
-
backgroundColor: '#
|
|
97
|
-
borderColor: '#
|
|
96
|
+
backgroundColor: '#F4F4F5',
|
|
97
|
+
borderColor: '#E4E4E7',
|
|
98
98
|
borderRadius: 13,
|
|
99
99
|
borderWidth: 1,
|
|
100
100
|
marginRight: 7,
|
|
@@ -102,20 +102,20 @@ export const styles = StyleSheet.create({
|
|
|
102
102
|
paddingHorizontal: 10,
|
|
103
103
|
paddingVertical: 6,
|
|
104
104
|
},
|
|
105
|
-
agentMetaText: { color: '#
|
|
105
|
+
agentMetaText: { color: '#3F3F46', fontSize: 12, fontWeight: '800' },
|
|
106
106
|
emptyPanel: {
|
|
107
|
-
backgroundColor: '#
|
|
108
|
-
borderColor: '#
|
|
107
|
+
backgroundColor: '#FFFFFF',
|
|
108
|
+
borderColor: '#E4E4E7',
|
|
109
109
|
borderRadius: 22,
|
|
110
110
|
borderWidth: 1,
|
|
111
111
|
padding: 16,
|
|
112
112
|
},
|
|
113
|
-
emptyTitle: { color: '#
|
|
114
|
-
emptyBody: { color: '#
|
|
113
|
+
emptyTitle: { color: '#18181B', fontSize: 23, fontWeight: '900', lineHeight: 28, marginTop: 15 },
|
|
114
|
+
emptyBody: { color: '#52525B', fontSize: 14, fontWeight: '600', lineHeight: 21, marginTop: 8 },
|
|
115
115
|
emptyChipRow: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 14 },
|
|
116
116
|
emptyChip: {
|
|
117
|
-
backgroundColor: '#
|
|
118
|
-
borderColor: '#
|
|
117
|
+
backgroundColor: '#F4F4F5',
|
|
118
|
+
borderColor: '#E4E4E7',
|
|
119
119
|
borderRadius: 15,
|
|
120
120
|
borderWidth: 1,
|
|
121
121
|
marginRight: 7,
|
|
@@ -123,21 +123,21 @@ export const styles = StyleSheet.create({
|
|
|
123
123
|
paddingHorizontal: 11,
|
|
124
124
|
paddingVertical: 7,
|
|
125
125
|
},
|
|
126
|
-
emptyChipText: { color: '#
|
|
126
|
+
emptyChipText: { color: '#3F3F46', fontSize: 12, fontWeight: '800' },
|
|
127
127
|
primaryButton: {
|
|
128
128
|
alignItems: 'center',
|
|
129
|
-
backgroundColor: '#
|
|
129
|
+
backgroundColor: '#18181B',
|
|
130
130
|
borderRadius: 17,
|
|
131
131
|
justifyContent: 'center',
|
|
132
132
|
marginTop: 20,
|
|
133
133
|
minHeight: 50,
|
|
134
134
|
paddingHorizontal: 18,
|
|
135
135
|
},
|
|
136
|
-
primaryButtonText: { color: '#
|
|
136
|
+
primaryButtonText: { color: '#FFFFFF', fontSize: 16, fontWeight: '900' },
|
|
137
137
|
secondaryButton: {
|
|
138
138
|
alignItems: 'center',
|
|
139
|
-
backgroundColor: '#
|
|
140
|
-
borderColor: '#
|
|
139
|
+
backgroundColor: '#FFFFFF',
|
|
140
|
+
borderColor: '#E4E4E7',
|
|
141
141
|
borderRadius: 16,
|
|
142
142
|
borderWidth: 1,
|
|
143
143
|
flexDirection: 'row',
|
|
@@ -146,7 +146,7 @@ export const styles = StyleSheet.create({
|
|
|
146
146
|
minHeight: 46,
|
|
147
147
|
paddingHorizontal: 16,
|
|
148
148
|
},
|
|
149
|
-
secondaryButtonText: { color: '#
|
|
149
|
+
secondaryButtonText: { color: '#18181B', fontSize: 14, fontWeight: '900', marginLeft: 7 },
|
|
150
150
|
recentSection: { marginTop: 22 },
|
|
151
151
|
sectionHeader: {
|
|
152
152
|
alignItems: 'center',
|
|
@@ -154,12 +154,12 @@ export const styles = StyleSheet.create({
|
|
|
154
154
|
justifyContent: 'space-between',
|
|
155
155
|
marginBottom: 10,
|
|
156
156
|
},
|
|
157
|
-
sectionTitle: { color: '#
|
|
158
|
-
sectionCount: { color: '#
|
|
157
|
+
sectionTitle: { color: '#18181B', fontSize: 17, fontWeight: '900' },
|
|
158
|
+
sectionCount: { color: '#A1A1AA', fontSize: 12, fontWeight: '900' },
|
|
159
159
|
agentRow: {
|
|
160
160
|
alignItems: 'center',
|
|
161
|
-
backgroundColor: '#
|
|
162
|
-
borderColor: '#
|
|
161
|
+
backgroundColor: '#FFFFFF',
|
|
162
|
+
borderColor: '#E4E4E7',
|
|
163
163
|
borderRadius: 17,
|
|
164
164
|
borderWidth: 1,
|
|
165
165
|
flexDirection: 'row',
|
|
@@ -170,12 +170,12 @@ export const styles = StyleSheet.create({
|
|
|
170
170
|
paddingVertical: 10,
|
|
171
171
|
},
|
|
172
172
|
agentRowBody: { flex: 1, marginLeft: 11, minWidth: 0 },
|
|
173
|
-
agentRowName: { color: '#
|
|
174
|
-
agentRowDescription: { color: '#
|
|
173
|
+
agentRowName: { color: '#18181B', fontSize: 15, fontWeight: '900' },
|
|
174
|
+
agentRowDescription: { color: '#71717A', fontSize: 12, fontWeight: '600', lineHeight: 17, marginTop: 3 },
|
|
175
175
|
compactAvatar: {
|
|
176
176
|
alignItems: 'center',
|
|
177
|
-
backgroundColor: '#
|
|
178
|
-
borderColor: '#
|
|
177
|
+
backgroundColor: '#F4F4F5',
|
|
178
|
+
borderColor: '#E4E4E7',
|
|
179
179
|
borderRadius: 15,
|
|
180
180
|
borderWidth: 1,
|
|
181
181
|
height: 38,
|
|
@@ -185,6 +185,6 @@ export const styles = StyleSheet.create({
|
|
|
185
185
|
placeholder: { flex: 1, padding: 20 },
|
|
186
186
|
backButton: { alignSelf: 'flex-start', marginBottom: 24, paddingVertical: 8 },
|
|
187
187
|
backButtonText: { color: '#FF5A1F', fontSize: 16, fontWeight: '700' },
|
|
188
|
-
helperText: { color: '#
|
|
188
|
+
helperText: { color: '#A1A1AA', fontSize: 12, fontWeight: '700', lineHeight: 18, marginTop: 12 },
|
|
189
189
|
pressed: { opacity: 0.72 },
|
|
190
190
|
});
|
package/src/types.ts
CHANGED
|
@@ -421,6 +421,7 @@ export type SuperagentMarkdownRendererProps = {
|
|
|
421
421
|
export type SuperagentMarkdownRenderer = ComponentType<SuperagentMarkdownRendererProps>;
|
|
422
422
|
|
|
423
423
|
export type SuperagentHomeScreenProps = {
|
|
424
|
+
activeAgentId?: string | null;
|
|
424
425
|
agents?: SuperagentAgent[];
|
|
425
426
|
latestMessages?: SuperagentMessage[];
|
|
426
427
|
messagesByAgentId?: Record<string, SuperagentMessage[]>;
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
hasNewAssistantResponse,
|
|
10
10
|
isQueuedResponse,
|
|
11
11
|
isVisibleMessage,
|
|
12
|
+
mergeConversationSnapshot,
|
|
12
13
|
mergeMessage,
|
|
13
14
|
normalizeMessages,
|
|
14
15
|
pollConversation,
|
|
@@ -48,6 +49,35 @@ export function useSuperagentConversation({
|
|
|
48
49
|
const [initError, setInitError] = useState<string | null>(null);
|
|
49
50
|
const [hasMoreMessages, setHasMoreMessages] = useState(false);
|
|
50
51
|
const sendGenerationRef = useRef(0);
|
|
52
|
+
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
53
|
+
const loadingPreviousRef = useRef(false);
|
|
54
|
+
const settleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
55
|
+
const messagesRef = useRef(messages);
|
|
56
|
+
const onAgentMessageDoneRef = useRef(onAgentMessageDone);
|
|
57
|
+
const onConversationSettledRef = useRef(onConversationSettled);
|
|
58
|
+
|
|
59
|
+
useEffect(() => { messagesRef.current = messages; }, [messages]);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
onAgentMessageDoneRef.current = onAgentMessageDone;
|
|
62
|
+
onConversationSettledRef.current = onConversationSettled;
|
|
63
|
+
}, [onAgentMessageDone, onConversationSettled]);
|
|
64
|
+
|
|
65
|
+
// Cancel any in-flight poll/settle timers — called on each new send and on unmount.
|
|
66
|
+
const clearPendingSettlers = useCallback(() => {
|
|
67
|
+
if (pollIntervalRef.current) {
|
|
68
|
+
clearInterval(pollIntervalRef.current);
|
|
69
|
+
pollIntervalRef.current = null;
|
|
70
|
+
}
|
|
71
|
+
if (settleTimeoutRef.current) {
|
|
72
|
+
clearTimeout(settleTimeoutRef.current);
|
|
73
|
+
settleTimeoutRef.current = null;
|
|
74
|
+
}
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Clear any active poll interval / settle timeout when the hook unmounts.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
return () => clearPendingSettlers();
|
|
80
|
+
}, [clearPendingSettlers]);
|
|
51
81
|
|
|
52
82
|
useEffect(() => {
|
|
53
83
|
if (!apiClient) setMessages(fallbackMessages);
|
|
@@ -94,34 +124,45 @@ export function useSuperagentConversation({
|
|
|
94
124
|
|
|
95
125
|
return realtimeClient.subscribeToConversation(conversationId, {
|
|
96
126
|
onMessage(message) {
|
|
127
|
+
// Don't clear the sending/stop state on intermediate assistant messages
|
|
128
|
+
// (tool-call steps or streamed content arrive before the turn is done).
|
|
129
|
+
// The turn ends on `agent_done` (onAgentDone) or the 5s settle fallback.
|
|
97
130
|
setMessages((current) => mergeMessage(current, message));
|
|
98
|
-
if (message.role === 'assistant' && (message.content || message.toolCalls?.length || message.tool_calls?.length)) {
|
|
99
|
-
setIsSending(false);
|
|
100
|
-
}
|
|
101
131
|
},
|
|
102
132
|
onConversation(conversation) {
|
|
103
133
|
if (conversation.id !== conversationId) return;
|
|
104
|
-
|
|
134
|
+
const incoming = normalizeMessages(conversation.messages ?? []);
|
|
135
|
+
setMessages((current) => mergeConversationSnapshot(current, incoming));
|
|
105
136
|
},
|
|
106
137
|
onAgentDone() {
|
|
138
|
+
// Agent finished via realtime — cancel the 5s settle fallback so
|
|
139
|
+
// onConversationSettled doesn't also fire when the timer elapses.
|
|
140
|
+
if (settleTimeoutRef.current) {
|
|
141
|
+
clearTimeout(settleTimeoutRef.current);
|
|
142
|
+
settleTimeoutRef.current = null;
|
|
143
|
+
}
|
|
107
144
|
setIsSending(false);
|
|
108
|
-
|
|
145
|
+
setQueuedMessages([]);
|
|
146
|
+
onAgentMessageDoneRef.current?.();
|
|
109
147
|
if (apiClient) {
|
|
110
148
|
refreshConversation(apiClient, conversationId, setMessages)
|
|
111
|
-
.then(() =>
|
|
149
|
+
.then(() => onConversationSettledRef.current?.())
|
|
112
150
|
.catch(() => {});
|
|
113
151
|
} else {
|
|
114
|
-
|
|
152
|
+
onConversationSettledRef.current?.();
|
|
115
153
|
}
|
|
116
154
|
},
|
|
117
155
|
onReconnect() {
|
|
156
|
+
// A reconnect means the transient socket error cleared — drop the
|
|
157
|
+
// "something went wrong" state so it doesn't linger as a failed load.
|
|
158
|
+
setInitError(null);
|
|
118
159
|
if (apiClient) refreshConversation(apiClient, conversationId, setMessages).catch(() => {});
|
|
119
160
|
},
|
|
120
161
|
onError(error) {
|
|
121
162
|
setInitError(error instanceof Error ? error.message : 'Realtime connection failed');
|
|
122
163
|
},
|
|
123
164
|
});
|
|
124
|
-
}, [apiClient, conversationId,
|
|
165
|
+
}, [apiClient, conversationId, realtimeClient]);
|
|
125
166
|
|
|
126
167
|
const visibleMessages = useMemo(
|
|
127
168
|
() => messages.filter((message, index) => isVisibleMessage(message, index)),
|
|
@@ -133,12 +174,20 @@ export function useSuperagentConversation({
|
|
|
133
174
|
const fileUrls = options.fileUrls?.filter(Boolean) ?? [];
|
|
134
175
|
if (!trimmedContent && fileUrls.length === 0) return;
|
|
135
176
|
|
|
136
|
-
|
|
177
|
+
// A new send supersedes any pending poll/settle timers from the previous one.
|
|
178
|
+
clearPendingSettlers();
|
|
179
|
+
|
|
180
|
+
const assistantCountBefore = countAssistantResponses(messagesRef.current);
|
|
137
181
|
const userMessage = createUserMessage(trimmedContent, { fileUrls, replyTo: options.replyTo });
|
|
138
182
|
setMessages((current) => [...current, userMessage]);
|
|
139
183
|
|
|
140
184
|
if (!apiClient || !conversationId) {
|
|
141
|
-
if (!onSendMessage)
|
|
185
|
+
if (!onSendMessage) {
|
|
186
|
+
// No send path yet (no fallback handler, or the conversation is still
|
|
187
|
+
// loading) — drop the optimistic bubble so the turn doesn't look sent.
|
|
188
|
+
setMessages((current) => current.filter((message) => message.id !== userMessage.id));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
142
191
|
const sendGeneration = sendGenerationRef.current + 1;
|
|
143
192
|
sendGenerationRef.current = sendGeneration;
|
|
144
193
|
const setSendingForCurrentSend = (value: boolean) => {
|
|
@@ -147,7 +196,7 @@ export function useSuperagentConversation({
|
|
|
147
196
|
|
|
148
197
|
setIsSending(true);
|
|
149
198
|
const hasAssistantResponse = await sendWithFallback(agentId, trimmedContent, fileUrls, options.replyTo, onSendMessage, setMessages, setSendingForCurrentSend);
|
|
150
|
-
if (hasAssistantResponse)
|
|
199
|
+
if (hasAssistantResponse) onAgentMessageDoneRef.current?.();
|
|
151
200
|
return;
|
|
152
201
|
}
|
|
153
202
|
|
|
@@ -159,24 +208,41 @@ export function useSuperagentConversation({
|
|
|
159
208
|
|
|
160
209
|
setIsSending(true);
|
|
161
210
|
try {
|
|
211
|
+
// Wait for the turn to complete: poll without realtime, otherwise a 5s
|
|
212
|
+
// fallback in case agent_done never arrives.
|
|
213
|
+
const armSettleFallback = () => {
|
|
214
|
+
if (!realtimeClient) {
|
|
215
|
+
pollIntervalRef.current = pollConversation(
|
|
216
|
+
apiClient, conversationId, setMessages, setSendingForCurrentSend, assistantCountBefore,
|
|
217
|
+
() => { setQueuedMessages([]); onConversationSettledRef.current?.(); },
|
|
218
|
+
() => onAgentMessageDoneRef.current?.(),
|
|
219
|
+
);
|
|
220
|
+
} else {
|
|
221
|
+
settleTimeoutRef.current = setTimeout(() => {
|
|
222
|
+
settleTimeoutRef.current = null;
|
|
223
|
+
refreshConversation(apiClient, conversationId, setMessages).catch(() => {});
|
|
224
|
+
setSendingForCurrentSend(false);
|
|
225
|
+
setQueuedMessages([]);
|
|
226
|
+
onAgentMessageDoneRef.current?.();
|
|
227
|
+
onConversationSettledRef.current?.();
|
|
228
|
+
}, 5000);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
162
232
|
const response = await apiClient.addMessage(conversationId, userMessage);
|
|
163
233
|
if (isQueuedResponse(response)) {
|
|
164
234
|
setQueuedMessages((current) => [...current, { id: userMessage.id!, content: userMessage.content, position: current.length }]);
|
|
235
|
+
// A queued send still needs a settle path, or isSending / the queued strip stick.
|
|
236
|
+
armSettleFallback();
|
|
165
237
|
} else {
|
|
166
238
|
const refreshedMessages = await refreshConversation(apiClient, conversationId, setMessages);
|
|
167
|
-
if (!realtimeClient) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
} else {
|
|
173
|
-
pollConversation(apiClient, conversationId, setMessages, setSendingForCurrentSend, assistantCountBefore, onConversationSettled, onAgentMessageDone);
|
|
174
|
-
}
|
|
239
|
+
if (!realtimeClient && hasNewAssistantResponse(refreshedMessages, assistantCountBefore)) {
|
|
240
|
+
setSendingForCurrentSend(false);
|
|
241
|
+
setQueuedMessages([]);
|
|
242
|
+
onAgentMessageDoneRef.current?.();
|
|
243
|
+
onConversationSettledRef.current?.();
|
|
175
244
|
} else {
|
|
176
|
-
|
|
177
|
-
setSendingForCurrentSend(false);
|
|
178
|
-
onConversationSettled?.();
|
|
179
|
-
}, 5000);
|
|
245
|
+
armSettleFallback();
|
|
180
246
|
}
|
|
181
247
|
}
|
|
182
248
|
} catch (error) {
|
|
@@ -184,7 +250,7 @@ export function useSuperagentConversation({
|
|
|
184
250
|
setMessages((current) => [...current, errorMessage]);
|
|
185
251
|
setSendingForCurrentSend(false);
|
|
186
252
|
}
|
|
187
|
-
}, [agentId, apiClient,
|
|
253
|
+
}, [agentId, apiClient, clearPendingSettlers, conversationId, onSendMessage, realtimeClient]);
|
|
188
254
|
|
|
189
255
|
const deleteMessage = useCallback(async (messageId: string) => {
|
|
190
256
|
if (!messageId || messageId === 'welcome') return false;
|
|
@@ -196,16 +262,23 @@ export function useSuperagentConversation({
|
|
|
196
262
|
|
|
197
263
|
try {
|
|
198
264
|
await apiClient.deleteMessage(conversationId, messageId);
|
|
265
|
+
// Remove locally before refreshing: mergeConversationSnapshot keeps rows
|
|
266
|
+
// missing from the server snapshot, so without this the just-deleted
|
|
267
|
+
// message would be resurrected.
|
|
268
|
+
setMessages((current) => current.filter((message) => message.id !== messageId));
|
|
199
269
|
await refreshConversation(apiClient, conversationId, setMessages);
|
|
200
270
|
return true;
|
|
201
|
-
} catch
|
|
202
|
-
|
|
271
|
+
} catch {
|
|
272
|
+
// A failed delete is not a load failure — don't drive the "something went
|
|
273
|
+
// wrong" panel (it would persist). Signal failure via the return value so
|
|
274
|
+
// the caller can surface it.
|
|
203
275
|
return false;
|
|
204
276
|
}
|
|
205
277
|
}, [apiClient, conversationId]);
|
|
206
278
|
|
|
207
279
|
const stop = useCallback(async () => {
|
|
208
280
|
sendGenerationRef.current += 1;
|
|
281
|
+
clearPendingSettlers();
|
|
209
282
|
|
|
210
283
|
if (!apiClient || !conversationId) {
|
|
211
284
|
setIsSending(false);
|
|
@@ -216,20 +289,32 @@ export function useSuperagentConversation({
|
|
|
216
289
|
setQueuedMessages([]);
|
|
217
290
|
await apiClient.stopConversation(conversationId);
|
|
218
291
|
await refreshConversation(apiClient, conversationId, setMessages);
|
|
219
|
-
}, [apiClient, conversationId]);
|
|
292
|
+
}, [apiClient, clearPendingSettlers, conversationId]);
|
|
220
293
|
|
|
221
294
|
const loadPrevious = useCallback(async () => {
|
|
295
|
+
if (loadingPreviousRef.current) return;
|
|
222
296
|
const before = getMessageCursor(messages[0]);
|
|
223
297
|
if (!apiClient || !conversationId || !before) return;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
298
|
+
loadingPreviousRef.current = true;
|
|
299
|
+
try {
|
|
300
|
+
const page = await apiClient.getMessages(conversationId, { limit: MESSAGES_PAGE_SIZE, before });
|
|
301
|
+
setMessages((current) => [...normalizeMessages(page.messages), ...current]);
|
|
302
|
+
setHasMoreMessages(Boolean(page.has_more ?? page.hasMore));
|
|
303
|
+
} catch {
|
|
304
|
+
// Pagination failed — swallow so it isn't an unhandled rejection from the
|
|
305
|
+
// button, and leave hasMoreMessages as-is so the user can retry.
|
|
306
|
+
} finally {
|
|
307
|
+
loadingPreviousRef.current = false;
|
|
308
|
+
}
|
|
227
309
|
}, [apiClient, conversationId, messages]);
|
|
228
310
|
|
|
229
311
|
const submitToolCallInput = useCallback(async (toolCallId: string, approve: boolean, extraUserInput?: unknown, originRequestId?: string): Promise<SuperagentConversation | null> => {
|
|
230
312
|
if (!apiClient || !conversationId) return null;
|
|
231
313
|
const updated = await apiClient.submitToolCallInput(conversationId, toolCallId, approve, extraUserInput, originRequestId);
|
|
232
|
-
if (updated.messages)
|
|
314
|
+
if (updated.messages) {
|
|
315
|
+
const incoming = normalizeMessages(updated.messages);
|
|
316
|
+
setMessages((current) => mergeConversationSnapshot(current, incoming));
|
|
317
|
+
}
|
|
233
318
|
onConversationSettled?.();
|
|
234
319
|
return updated;
|
|
235
320
|
}, [apiClient, conversationId, onConversationSettled]);
|