@adminide-stack/yantra-mobile 12.0.28-alpha.51 → 12.0.28-alpha.58
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/lib/assets/icon.png +0 -0
- package/lib/components/CustomDrawer.js +358 -0
- package/lib/components/CustomDrawer.js.map +1 -0
- package/lib/components/GatewayToolbarButtonMobile.js +84 -0
- package/lib/components/GatewayToolbarButtonMobile.js.map +1 -0
- package/lib/components/YantraBrandLoader.js +94 -0
- package/lib/components/YantraBrandLoader.js.map +1 -0
- package/lib/compute.js +37 -5
- package/lib/compute.js.map +1 -1
- package/lib/config/constants.js +16 -0
- package/lib/config/constants.js.map +1 -0
- package/lib/config/env-config.js +74 -19
- package/lib/config/env-config.js.map +1 -1
- package/lib/contexts/GatewayContext.js +77 -0
- package/lib/contexts/GatewayContext.js.map +1 -0
- package/lib/graphql/agentGatewayDocuments.js +53 -0
- package/lib/graphql/agentGatewayDocuments.js.map +1 -0
- package/lib/hooks/useCdecliAutoConnect.js +219 -0
- package/lib/hooks/useCdecliAutoConnect.js.map +1 -0
- package/lib/hooks/useCdecliChannel.js +226 -0
- package/lib/hooks/useCdecliChannel.js.map +1 -0
- package/lib/hooks/useChatApi.js +220 -170
- package/lib/hooks/useChatApi.js.map +1 -1
- package/lib/hooks/useChatStream.js +232 -58
- package/lib/hooks/useChatStream.js.map +1 -1
- package/lib/hooks/useGatewayConnection.js +123 -0
- package/lib/hooks/useGatewayConnection.js.map +1 -0
- package/lib/hooks/useGatewayRegistry.js +28 -0
- package/lib/hooks/useGatewayRegistry.js.map +1 -0
- package/lib/hooks/usePrerequisiteIds.js +122 -0
- package/lib/hooks/usePrerequisiteIds.js.map +1 -0
- package/lib/hooks/useWorkspaceProvisioner.js +236 -0
- package/lib/hooks/useWorkspaceProvisioner.js.map +1 -0
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/routes.json +8 -5
- package/lib/screens/Home/HomeScreen.js +420 -97
- package/lib/screens/Home/HomeScreen.js.map +1 -1
- package/lib/screens/Home/components/ChatHistoryLanding.js +229 -0
- package/lib/screens/Home/components/ChatHistoryLanding.js.map +1 -0
- package/lib/screens/Home/components/DeepSearchModal.js +334 -0
- package/lib/screens/Home/components/DeepSearchModal.js.map +1 -0
- package/lib/screens/Home/deepSearchUtils.js +41 -0
- package/lib/screens/Home/deepSearchUtils.js.map +1 -0
- package/lib/screens/NewChat/index.js +75 -0
- package/lib/screens/NewChat/index.js.map +1 -0
- package/lib/services/agentSessionManager.js +451 -0
- package/lib/services/agentSessionManager.js.map +1 -0
- package/lib/services/gatewayApiKeyBridge.js +4 -0
- package/lib/services/gatewayApiKeyBridge.js.map +1 -0
- package/lib/services/gatewayClient.js +470 -0
- package/lib/services/gatewayClient.js.map +1 -0
- package/lib/utils/gatewaySelectionStorage.js +21 -0
- package/lib/utils/gatewaySelectionStorage.js.map +1 -0
- package/package.json +7 -3
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import {useState,useRef,useEffect
|
|
1
|
+
import {useState,useRef,useCallback,useMemo,useEffect}from'react';import {useChatMutations,useChatMessages}from'./useChatApi.js';import {useCdecliChannel}from'./useCdecliChannel.js';import {streamChatResponse}from'../api/chatApi.js';const REVEAL_CHUNK_SIZE = 6;
|
|
2
2
|
const REVEAL_INTERVAL_MS = 20;
|
|
3
|
-
function
|
|
3
|
+
function isCdecliAgentFailureOutput(text) {
|
|
4
|
+
const t = text.trim();
|
|
5
|
+
if (!t) return false;
|
|
6
|
+
return /⚠\s*Error:/.test(t) || /LLM stream error/i.test(t) || /Incorrect API key/i.test(t) || /invalid_api_key/i.test(t) || /authentication_error/i.test(t);
|
|
7
|
+
}
|
|
8
|
+
function useChatStream(sessionId, routing = {
|
|
9
|
+
kind: "groq"
|
|
10
|
+
}) {
|
|
4
11
|
const [messages, setMessages] = useState([]);
|
|
5
12
|
const [response, setResponse] = useState("");
|
|
6
13
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -9,68 +16,26 @@ function useChatStream(sessionId) {
|
|
|
9
16
|
const abortRef = useRef(null);
|
|
10
17
|
const isSendingRef = useRef(false);
|
|
11
18
|
const revealIntervalRef = useRef(null);
|
|
19
|
+
const [cdecliStreamOverrideId, setCdecliStreamOverrideId] = useState(void 0);
|
|
20
|
+
const userMsgForSaveRef = useRef(null);
|
|
21
|
+
const saveSessionIdRef = useRef(null);
|
|
22
|
+
const cdecliRoundHandledRef = useRef(false);
|
|
23
|
+
const cdecliFallbackConversationRef = useRef(null);
|
|
24
|
+
const routingRef = useRef(routing);
|
|
25
|
+
routingRef.current = routing;
|
|
12
26
|
const {
|
|
13
27
|
saveMessages
|
|
14
28
|
} = useChatMutations();
|
|
15
29
|
const {
|
|
16
30
|
messages: backendMessages
|
|
17
31
|
} = useChatMessages(sessionId);
|
|
18
|
-
|
|
19
|
-
if (revealIntervalRef.current) {
|
|
20
|
-
clearInterval(revealIntervalRef.current);
|
|
21
|
-
revealIntervalRef.current = null;
|
|
22
|
-
}
|
|
23
|
-
}, []);
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (isSendingRef.current) return;
|
|
26
|
-
if (!sessionId) {
|
|
27
|
-
setMessages([]);
|
|
28
|
-
setResponse("");
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (backendMessages && backendMessages.length > 0) {
|
|
32
|
-
const formatted = backendMessages.map((msg) => {
|
|
33
|
-
var _a;
|
|
34
|
-
return {
|
|
35
|
-
role: msg.role,
|
|
36
|
-
content: (_a = msg.content) != null ? _a : "",
|
|
37
|
-
metadata: msg.tokenCount ? {
|
|
38
|
-
tokenUsage: {
|
|
39
|
-
inputTokens: 0,
|
|
40
|
-
outputTokens: msg.tokenCount,
|
|
41
|
-
totalTokens: msg.tokenCount
|
|
42
|
-
},
|
|
43
|
-
model: msg.model
|
|
44
|
-
} : void 0
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
setMessages(formatted);
|
|
48
|
-
setResponse("");
|
|
49
|
-
}
|
|
50
|
-
}, [sessionId, backendMessages]);
|
|
51
|
-
const sendMessage = useCallback(async (content, attachments, sessionIdOverride) => {
|
|
32
|
+
const executeGroqStream = useCallback(async (conv, effectiveSessionId, userMessage) => {
|
|
52
33
|
var _a, _b;
|
|
53
|
-
if (!content.trim()) return;
|
|
54
|
-
const effectiveSessionId = sessionIdOverride !== void 0 ? sessionIdOverride : sessionId;
|
|
55
|
-
const userMessage = {
|
|
56
|
-
role: "user",
|
|
57
|
-
content: content.trim(),
|
|
58
|
-
attachments
|
|
59
|
-
};
|
|
60
|
-
isSendingRef.current = true;
|
|
61
|
-
setMessages((prev) => [...prev, userMessage]);
|
|
62
|
-
setResponse("");
|
|
63
|
-
setIsLoading(true);
|
|
64
|
-
setIsStreaming(true);
|
|
65
|
-
setError(null);
|
|
66
34
|
abortRef.current = new AbortController();
|
|
67
|
-
const chatMessages =
|
|
35
|
+
const chatMessages = conv.map((m) => ({
|
|
68
36
|
role: m.role,
|
|
69
37
|
content: m.content
|
|
70
|
-
}))
|
|
71
|
-
role: "user",
|
|
72
|
-
content: content.trim()
|
|
73
|
-
}];
|
|
38
|
+
}));
|
|
74
39
|
let doingChunkedReveal = false;
|
|
75
40
|
try {
|
|
76
41
|
const g = global;
|
|
@@ -127,12 +92,14 @@ function useChatStream(sessionId) {
|
|
|
127
92
|
tokenCount: (_b2 = result.tokenUsage) == null ? void 0 : _b2.totalTokens,
|
|
128
93
|
model: void 0
|
|
129
94
|
}
|
|
130
|
-
}).catch((e) => console.error("[useChatStream] saveMessages:", e));
|
|
95
|
+
}).catch((e) => console.error("[useChatStream] saveMessages (fallback):", e));
|
|
131
96
|
}
|
|
132
97
|
isSendingRef.current = false;
|
|
133
98
|
setIsLoading(false);
|
|
134
99
|
setIsStreaming(false);
|
|
135
100
|
abortRef.current = null;
|
|
101
|
+
userMsgForSaveRef.current = null;
|
|
102
|
+
saveSessionIdRef.current = null;
|
|
136
103
|
}
|
|
137
104
|
}, REVEAL_INTERVAL_MS);
|
|
138
105
|
return;
|
|
@@ -173,8 +140,7 @@ function useChatStream(sessionId) {
|
|
|
173
140
|
}
|
|
174
141
|
} catch (err) {
|
|
175
142
|
const msg = err instanceof Error ? err.message : String(err);
|
|
176
|
-
if (msg.includes("abort"))
|
|
177
|
-
setError(msg);
|
|
143
|
+
if (!msg.includes("abort")) setError(msg);
|
|
178
144
|
setResponse("");
|
|
179
145
|
} finally {
|
|
180
146
|
if (!doingChunkedReveal) {
|
|
@@ -182,9 +148,217 @@ function useChatStream(sessionId) {
|
|
|
182
148
|
setIsLoading(false);
|
|
183
149
|
setIsStreaming(false);
|
|
184
150
|
abortRef.current = null;
|
|
151
|
+
userMsgForSaveRef.current = null;
|
|
152
|
+
saveSessionIdRef.current = null;
|
|
185
153
|
}
|
|
186
154
|
}
|
|
187
|
-
}, [
|
|
155
|
+
}, [saveMessages]);
|
|
156
|
+
const streamChannelId = sessionId || cdecliStreamOverrideId;
|
|
157
|
+
const isCdecliActive = routing.kind === "cdecli" && routing.channelConnected;
|
|
158
|
+
const cdecliCallbacks = useMemo(() => ({
|
|
159
|
+
onChunk: (text) => {
|
|
160
|
+
if (routingRef.current.kind !== "cdecli") return;
|
|
161
|
+
setResponse((r) => r + text);
|
|
162
|
+
},
|
|
163
|
+
onComplete: (text) => {
|
|
164
|
+
var _a;
|
|
165
|
+
if (routingRef.current.kind !== "cdecli") return;
|
|
166
|
+
if (cdecliRoundHandledRef.current) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (isCdecliAgentFailureOutput(text)) {
|
|
170
|
+
cdecliRoundHandledRef.current = true;
|
|
171
|
+
console.warn("[useChatStream] CDeCLI agent error output; retrying via direct chat API");
|
|
172
|
+
const sid2 = saveSessionIdRef.current;
|
|
173
|
+
const um2 = userMsgForSaveRef.current;
|
|
174
|
+
const conv = cdecliFallbackConversationRef.current;
|
|
175
|
+
setResponse("");
|
|
176
|
+
setError(null);
|
|
177
|
+
userMsgForSaveRef.current = um2;
|
|
178
|
+
saveSessionIdRef.current = sid2;
|
|
179
|
+
setIsLoading(true);
|
|
180
|
+
setIsStreaming(true);
|
|
181
|
+
isSendingRef.current = true;
|
|
182
|
+
if ((conv == null ? void 0 : conv.length) && um2) {
|
|
183
|
+
void executeGroqStream(conv, sid2, um2);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
setMessages((prev) => [...prev, {
|
|
187
|
+
role: "assistant",
|
|
188
|
+
content: text
|
|
189
|
+
}]);
|
|
190
|
+
setIsLoading(false);
|
|
191
|
+
setIsStreaming(false);
|
|
192
|
+
isSendingRef.current = false;
|
|
193
|
+
userMsgForSaveRef.current = null;
|
|
194
|
+
saveSessionIdRef.current = null;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
cdecliRoundHandledRef.current = true;
|
|
198
|
+
const sid = saveSessionIdRef.current;
|
|
199
|
+
const um = userMsgForSaveRef.current;
|
|
200
|
+
saveSessionIdRef.current = null;
|
|
201
|
+
userMsgForSaveRef.current = null;
|
|
202
|
+
setMessages((prev) => [...prev, {
|
|
203
|
+
role: "assistant",
|
|
204
|
+
content: text
|
|
205
|
+
}]);
|
|
206
|
+
setResponse("");
|
|
207
|
+
setIsLoading(false);
|
|
208
|
+
setIsStreaming(false);
|
|
209
|
+
isSendingRef.current = false;
|
|
210
|
+
const rt = routingRef.current;
|
|
211
|
+
const persistClientSide = rt.kind === "cdecli" && rt.persistenceMode !== "backend";
|
|
212
|
+
if (sid && um && persistClientSide) {
|
|
213
|
+
void saveMessages({
|
|
214
|
+
sessionId: sid,
|
|
215
|
+
userMessage: {
|
|
216
|
+
content: um.content,
|
|
217
|
+
attachments: (_a = um.attachments) == null ? void 0 : _a.map((a) => ({
|
|
218
|
+
id: a.id,
|
|
219
|
+
name: a.name,
|
|
220
|
+
type: a.type,
|
|
221
|
+
mimeType: a.mimeType,
|
|
222
|
+
size: a.size,
|
|
223
|
+
url: a.url
|
|
224
|
+
}))
|
|
225
|
+
},
|
|
226
|
+
assistantMessage: {
|
|
227
|
+
content: text,
|
|
228
|
+
tokenCount: void 0,
|
|
229
|
+
model: "cdecli-serve"
|
|
230
|
+
}
|
|
231
|
+
}).catch((e) => console.error("[useChatStream] saveMessages (cdecli):", e));
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
onError: (err) => {
|
|
235
|
+
if (routingRef.current.kind !== "cdecli") return;
|
|
236
|
+
if (cdecliRoundHandledRef.current) return;
|
|
237
|
+
const conv = cdecliFallbackConversationRef.current;
|
|
238
|
+
const um = userMsgForSaveRef.current;
|
|
239
|
+
const sid = saveSessionIdRef.current;
|
|
240
|
+
if ((conv == null ? void 0 : conv.length) && um) {
|
|
241
|
+
cdecliRoundHandledRef.current = true;
|
|
242
|
+
console.warn("[useChatStream] CDeCLI transport/stream error; falling back to direct chat API:", err);
|
|
243
|
+
setResponse("");
|
|
244
|
+
setError(null);
|
|
245
|
+
userMsgForSaveRef.current = um;
|
|
246
|
+
saveSessionIdRef.current = sid;
|
|
247
|
+
setIsLoading(true);
|
|
248
|
+
setIsStreaming(true);
|
|
249
|
+
isSendingRef.current = true;
|
|
250
|
+
void executeGroqStream(conv, sid, um);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
cdecliRoundHandledRef.current = false;
|
|
254
|
+
setError(err);
|
|
255
|
+
setResponse("");
|
|
256
|
+
setIsLoading(false);
|
|
257
|
+
setIsStreaming(false);
|
|
258
|
+
isSendingRef.current = false;
|
|
259
|
+
userMsgForSaveRef.current = null;
|
|
260
|
+
saveSessionIdRef.current = null;
|
|
261
|
+
}
|
|
262
|
+
}), [saveMessages, executeGroqStream]);
|
|
263
|
+
const {
|
|
264
|
+
sendMessage: sendCdecliMessage
|
|
265
|
+
} = useCdecliChannel(routing.kind === "cdecli", isCdecliActive, routing.kind === "cdecli" ? routing.accountId : "default", streamChannelId, cdecliCallbacks);
|
|
266
|
+
useEffect(() => () => {
|
|
267
|
+
if (revealIntervalRef.current) {
|
|
268
|
+
clearInterval(revealIntervalRef.current);
|
|
269
|
+
revealIntervalRef.current = null;
|
|
270
|
+
}
|
|
271
|
+
}, []);
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (sessionId && cdecliStreamOverrideId && sessionId === cdecliStreamOverrideId) {
|
|
274
|
+
setCdecliStreamOverrideId(void 0);
|
|
275
|
+
}
|
|
276
|
+
}, [sessionId, cdecliStreamOverrideId]);
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
if (isSendingRef.current) return;
|
|
279
|
+
if (!sessionId) {
|
|
280
|
+
setMessages([]);
|
|
281
|
+
setResponse("");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (backendMessages && backendMessages.length > 0) {
|
|
285
|
+
const formatted = backendMessages.map((msg) => {
|
|
286
|
+
var _a;
|
|
287
|
+
return {
|
|
288
|
+
role: msg.role,
|
|
289
|
+
content: (_a = msg.content) != null ? _a : "",
|
|
290
|
+
metadata: msg.tokenCount ? {
|
|
291
|
+
tokenUsage: {
|
|
292
|
+
inputTokens: 0,
|
|
293
|
+
outputTokens: msg.tokenCount,
|
|
294
|
+
totalTokens: msg.tokenCount
|
|
295
|
+
},
|
|
296
|
+
model: msg.model
|
|
297
|
+
} : void 0
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
setMessages(formatted);
|
|
301
|
+
setResponse("");
|
|
302
|
+
}
|
|
303
|
+
}, [sessionId, backendMessages]);
|
|
304
|
+
const sendMessage = useCallback(async (content, attachments, sessionIdOverride) => {
|
|
305
|
+
if (!content.trim()) return;
|
|
306
|
+
const effectiveSessionId = sessionIdOverride !== void 0 ? sessionIdOverride : sessionId;
|
|
307
|
+
const userMessage = {
|
|
308
|
+
role: "user",
|
|
309
|
+
content: content.trim(),
|
|
310
|
+
attachments
|
|
311
|
+
};
|
|
312
|
+
isSendingRef.current = true;
|
|
313
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
314
|
+
setResponse("");
|
|
315
|
+
setIsLoading(true);
|
|
316
|
+
setIsStreaming(true);
|
|
317
|
+
setError(null);
|
|
318
|
+
userMsgForSaveRef.current = userMessage;
|
|
319
|
+
saveSessionIdRef.current = effectiveSessionId != null ? effectiveSessionId : null;
|
|
320
|
+
cdecliRoundHandledRef.current = false;
|
|
321
|
+
const rt = routingRef.current;
|
|
322
|
+
if (rt.kind === "cdecli") {
|
|
323
|
+
if (!rt.channelConnected) {
|
|
324
|
+
setError("CDeCLI is not connected yet. Check gateway status in the header.");
|
|
325
|
+
setMessages((p) => p.slice(0, -1));
|
|
326
|
+
isSendingRef.current = false;
|
|
327
|
+
setIsLoading(false);
|
|
328
|
+
setIsStreaming(false);
|
|
329
|
+
userMsgForSaveRef.current = null;
|
|
330
|
+
saveSessionIdRef.current = null;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!effectiveSessionId) {
|
|
334
|
+
setError("Missing chat session id.");
|
|
335
|
+
setMessages((p) => p.slice(0, -1));
|
|
336
|
+
isSendingRef.current = false;
|
|
337
|
+
setIsLoading(false);
|
|
338
|
+
setIsStreaming(false);
|
|
339
|
+
userMsgForSaveRef.current = null;
|
|
340
|
+
saveSessionIdRef.current = null;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (sessionIdOverride) {
|
|
344
|
+
setCdecliStreamOverrideId(sessionIdOverride);
|
|
345
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
346
|
+
}
|
|
347
|
+
cdecliFallbackConversationRef.current = [...messages, userMessage];
|
|
348
|
+
const ok = await sendCdecliMessage(content.trim(), effectiveSessionId);
|
|
349
|
+
if (!ok && !cdecliRoundHandledRef.current) {
|
|
350
|
+
const conv = cdecliFallbackConversationRef.current;
|
|
351
|
+
console.warn("[useChatStream] CDeCLI send returned false; falling back to direct chat API");
|
|
352
|
+
cdecliRoundHandledRef.current = true;
|
|
353
|
+
userMsgForSaveRef.current = userMessage;
|
|
354
|
+
saveSessionIdRef.current = effectiveSessionId != null ? effectiveSessionId : null;
|
|
355
|
+
void executeGroqStream(conv != null ? conv : [], effectiveSessionId != null ? effectiveSessionId : null, userMessage);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
await executeGroqStream([...messages, userMessage], effectiveSessionId != null ? effectiveSessionId : null, userMessage);
|
|
361
|
+
}, [messages, sessionId, sendCdecliMessage, executeGroqStream]);
|
|
188
362
|
const cancel = useCallback(() => {
|
|
189
363
|
if (abortRef.current) {
|
|
190
364
|
abortRef.current.abort();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useChatStream.js","sources":["../../src/hooks/useChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useChatMutations, useChatMessages } from './useChatApi';\nimport { streamChatResponse } from '../api/chatApi';\n\n/** Chunk size for simulated streaming reveal (Kimi-style step-by-step) */\nconst REVEAL_CHUNK_SIZE = 6;\n/** Delay in ms between chunks */\nconst REVEAL_INTERVAL_MS = 20;\n\nexport interface MessageAttachment {\n id: string;\n name: string;\n type: 'file' | 'screenshot';\n dataUrl?: string;\n mimeType?: string;\n size?: number;\n url?: string;\n}\n\nexport interface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n attachments?: MessageAttachment[];\n metadata?: {\n tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number };\n model?: string;\n };\n}\n\nexport function useChatStream(sessionId: string | null) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [response, setResponse] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const isSendingRef = useRef(false);\n const revealIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const { saveMessages } = useChatMutations();\n const { messages: backendMessages } = useChatMessages(sessionId);\n\n useEffect(\n () => () => {\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n },\n [],\n );\n\n // Load messages from backend when session changes (skip if we're actively sending)\n useEffect(() => {\n if (isSendingRef.current) return;\n\n if (!sessionId) {\n setMessages([]);\n setResponse('');\n return;\n }\n // Only sync from backend when we have data - never clear when backend is empty\n // (we may have local messages from a send that just completed)\n if (backendMessages && backendMessages.length > 0) {\n const formatted: ChatMessage[] = backendMessages.map((msg) => ({\n role: msg.role as 'user' | 'assistant',\n content: msg.content ?? '',\n metadata: msg.tokenCount\n ? {\n tokenUsage: {\n inputTokens: 0,\n outputTokens: msg.tokenCount,\n totalTokens: msg.tokenCount,\n },\n model: msg.model,\n }\n : undefined,\n }));\n setMessages(formatted);\n setResponse('');\n }\n }, [sessionId, backendMessages]);\n\n const sendMessage = useCallback(\n async (content: string, attachments?: MessageAttachment[], sessionIdOverride?: string | null) => {\n if (!content.trim()) return;\n\n const effectiveSessionId = sessionIdOverride !== undefined ? sessionIdOverride : sessionId;\n\n const userMessage: ChatMessage = {\n role: 'user',\n content: content.trim(),\n attachments,\n };\n\n isSendingRef.current = true;\n setMessages((prev) => [...prev, userMessage]);\n setResponse('');\n setIsLoading(true);\n setIsStreaming(true);\n setError(null);\n\n abortRef.current = new AbortController();\n\n const chatMessages = [\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n { role: 'user' as const, content: content.trim() },\n ];\n\n let doingChunkedReveal = false;\n try {\n const g = global as Record<string, unknown>;\n const preferNonStreaming =\n typeof g?.nativeCallSyncHook === 'function' || typeof g?.__fbBatchedBridge !== 'undefined';\n const result = await streamChatResponse(chatMessages, undefined, abortRef.current.signal, {\n preferNonStreaming,\n });\n const fullContent = result.content;\n\n if (preferNonStreaming && fullContent.length > 0) {\n doingChunkedReveal = true;\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n let revealedLen = 0;\n revealIntervalRef.current = setInterval(() => {\n revealedLen = Math.min(revealedLen + REVEAL_CHUNK_SIZE, fullContent.length);\n setResponse(fullContent.slice(0, revealedLen));\n if (revealedLen >= fullContent.length) {\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content: fullContent,\n metadata: result.tokenUsage\n ? {\n tokenUsage: {\n inputTokens: result.tokenUsage.inputTokens,\n outputTokens: result.tokenUsage.outputTokens,\n totalTokens: result.tokenUsage.totalTokens,\n },\n }\n : undefined,\n };\n setMessages((prev) => [...prev, assistantMessage]);\n setResponse('');\n if (effectiveSessionId) {\n saveMessages({\n sessionId: effectiveSessionId,\n userMessage: {\n content: userMessage.content,\n attachments: userMessage.attachments?.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n mimeType: a.mimeType,\n size: a.size,\n url: a.url,\n })),\n },\n assistantMessage: {\n content: fullContent,\n tokenCount: result.tokenUsage?.totalTokens,\n model: undefined,\n },\n }).catch((e) => console.error('[useChatStream] saveMessages:', e));\n }\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n abortRef.current = null;\n }\n }, REVEAL_INTERVAL_MS);\n return;\n }\n\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content: fullContent,\n metadata: result.tokenUsage\n ? {\n tokenUsage: {\n inputTokens: result.tokenUsage.inputTokens,\n outputTokens: result.tokenUsage.outputTokens,\n totalTokens: result.tokenUsage.totalTokens,\n },\n }\n : undefined,\n };\n\n setMessages((prev) => [...prev, assistantMessage]);\n setResponse('');\n\n if (effectiveSessionId) {\n await saveMessages({\n sessionId: effectiveSessionId,\n userMessage: {\n content: userMessage.content,\n attachments: userMessage.attachments?.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n mimeType: a.mimeType,\n size: a.size,\n url: a.url,\n })),\n },\n assistantMessage: {\n content: fullContent,\n tokenCount: result.tokenUsage?.totalTokens,\n model: undefined,\n },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('abort')) return;\n setError(msg);\n setResponse('');\n } finally {\n if (!doingChunkedReveal) {\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n abortRef.current = null;\n }\n }\n },\n [messages, sessionId, saveMessages],\n );\n\n const cancel = useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort();\n }\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n setResponse('');\n setError(null);\n }, []);\n\n return {\n messages,\n response,\n error,\n isLoading,\n isStreaming,\n hasMessages: messages.length > 0 || !!response,\n sendMessage,\n cancel,\n clearMessages,\n };\n}\n"],"names":["_a","_b","assistantMessage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useChatStream.js","sources":["../../src/hooks/useChatStream.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useChatMutations, useChatMessages } from './useChatApi';\nimport { useCdecliChannel } from './useCdecliChannel';\nimport { streamChatResponse } from '../api/chatApi';\n\nconst REVEAL_CHUNK_SIZE = 6;\nconst REVEAL_INTERVAL_MS = 20;\n\n/** Agent returned an error while the cdecli-serve channel stayed connected (e.g. bad LLM API key). */\nfunction isCdecliAgentFailureOutput(text: string): boolean {\n const t = text.trim();\n if (!t) return false;\n return (\n /⚠\\s*Error:/.test(t) ||\n /LLM stream error/i.test(t) ||\n /Incorrect API key/i.test(t) ||\n /invalid_api_key/i.test(t) ||\n /authentication_error/i.test(t)\n );\n}\n\nexport interface MessageAttachment {\n id: string;\n name: string;\n type: 'file' | 'screenshot';\n dataUrl?: string;\n mimeType?: string;\n size?: number;\n url?: string;\n}\n\nexport interface ChatMessage {\n role: 'user' | 'assistant';\n content: string;\n attachments?: MessageAttachment[];\n metadata?: {\n tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number };\n model?: string;\n };\n}\n\n/** When `kind` is `cdecli`, messages go through messenger-gateway (same as web CDeCLI). */\nexport type UseChatStreamRouting =\n | { kind: 'groq' }\n | {\n kind: 'cdecli';\n channelConnected: boolean;\n accountId: string;\n /** From `gatewayConnect` / gateway registry — when `'backend'`, server persists; skip client `saveMessages`. */\n persistenceMode?: 'backend' | 'frontend';\n };\n\nexport function useChatStream(sessionId: string | null, routing: UseChatStreamRouting = { kind: 'groq' }) {\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [response, setResponse] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const isSendingRef = useRef(false);\n const revealIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n /** Ensures GraphQL stream subscriptions use the id from createChannel+send before the next parent render. */\n const [cdecliStreamOverrideId, setCdecliStreamOverrideId] = useState<string | undefined>(undefined);\n const userMsgForSaveRef = useRef<ChatMessage | null>(null);\n const saveSessionIdRef = useRef<string | null>(null);\n /** Blocks duplicate cdecli `onComplete` from appending assistant twice / saving twice per send. */\n const cdecliRoundHandledRef = useRef(false);\n /** Snapshot of `[...priorMessages, userMessage]` for Groq fallback when CDeCLI send/stream fails. */\n const cdecliFallbackConversationRef = useRef<ChatMessage[] | null>(null);\n const routingRef = useRef(routing);\n routingRef.current = routing;\n\n const { saveMessages } = useChatMutations();\n const { messages: backendMessages } = useChatMessages(sessionId);\n\n const executeGroqStream = useCallback(\n async (conv: ChatMessage[], effectiveSessionId: string | null, userMessage: ChatMessage) => {\n abortRef.current = new AbortController();\n const chatMessages = conv.map((m) => ({ role: m.role, content: m.content }));\n let doingChunkedReveal = false;\n try {\n const g = global as Record<string, unknown>;\n const preferNonStreaming =\n typeof g?.nativeCallSyncHook === 'function' || typeof g?.__fbBatchedBridge !== 'undefined';\n const result = await streamChatResponse(chatMessages, undefined, abortRef.current.signal, {\n preferNonStreaming,\n });\n const fullContent = result.content;\n\n if (preferNonStreaming && fullContent.length > 0) {\n doingChunkedReveal = true;\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n let revealedLen = 0;\n revealIntervalRef.current = setInterval(() => {\n revealedLen = Math.min(revealedLen + REVEAL_CHUNK_SIZE, fullContent.length);\n setResponse(fullContent.slice(0, revealedLen));\n if (revealedLen >= fullContent.length) {\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content: fullContent,\n metadata: result.tokenUsage\n ? {\n tokenUsage: {\n inputTokens: result.tokenUsage.inputTokens,\n outputTokens: result.tokenUsage.outputTokens,\n totalTokens: result.tokenUsage.totalTokens,\n },\n }\n : undefined,\n };\n setMessages((prev) => [...prev, assistantMessage]);\n setResponse('');\n if (effectiveSessionId) {\n saveMessages({\n sessionId: effectiveSessionId,\n userMessage: {\n content: userMessage.content,\n attachments: userMessage.attachments?.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n mimeType: a.mimeType,\n size: a.size,\n url: a.url,\n })),\n },\n assistantMessage: {\n content: fullContent,\n tokenCount: result.tokenUsage?.totalTokens,\n model: undefined,\n },\n }).catch((e) => console.error('[useChatStream] saveMessages (fallback):', e));\n }\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n abortRef.current = null;\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n }\n }, REVEAL_INTERVAL_MS);\n return;\n }\n\n const assistantMessage: ChatMessage = {\n role: 'assistant',\n content: fullContent,\n metadata: result.tokenUsage\n ? {\n tokenUsage: {\n inputTokens: result.tokenUsage.inputTokens,\n outputTokens: result.tokenUsage.outputTokens,\n totalTokens: result.tokenUsage.totalTokens,\n },\n }\n : undefined,\n };\n\n setMessages((prev) => [...prev, assistantMessage]);\n setResponse('');\n\n if (effectiveSessionId) {\n await saveMessages({\n sessionId: effectiveSessionId,\n userMessage: {\n content: userMessage.content,\n attachments: userMessage.attachments?.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n mimeType: a.mimeType,\n size: a.size,\n url: a.url,\n })),\n },\n assistantMessage: {\n content: fullContent,\n tokenCount: result.tokenUsage?.totalTokens,\n model: undefined,\n },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes('abort')) setError(msg);\n setResponse('');\n } finally {\n if (!doingChunkedReveal) {\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n abortRef.current = null;\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n }\n }\n },\n [saveMessages],\n );\n\n const streamChannelId = sessionId || cdecliStreamOverrideId;\n\n const isCdecliActive = routing.kind === 'cdecli' && routing.channelConnected;\n\n const cdecliCallbacks = useMemo(\n () => ({\n onChunk: (text: string) => {\n if (routingRef.current.kind !== 'cdecli') return;\n setResponse((r) => r + text);\n },\n onComplete: (text: string) => {\n if (routingRef.current.kind !== 'cdecli') return;\n if (cdecliRoundHandledRef.current) {\n return;\n }\n\n if (isCdecliAgentFailureOutput(text)) {\n cdecliRoundHandledRef.current = true;\n console.warn('[useChatStream] CDeCLI agent error output; retrying via direct chat API');\n const sid = saveSessionIdRef.current;\n const um = userMsgForSaveRef.current;\n const conv = cdecliFallbackConversationRef.current;\n setResponse('');\n setError(null);\n userMsgForSaveRef.current = um;\n saveSessionIdRef.current = sid;\n setIsLoading(true);\n setIsStreaming(true);\n isSendingRef.current = true;\n if (conv?.length && um) {\n void executeGroqStream(conv, sid, um);\n return;\n }\n setMessages((prev) => [...prev, { role: 'assistant' as const, content: text }]);\n setIsLoading(false);\n setIsStreaming(false);\n isSendingRef.current = false;\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n return;\n }\n\n cdecliRoundHandledRef.current = true;\n\n const sid = saveSessionIdRef.current;\n const um = userMsgForSaveRef.current;\n saveSessionIdRef.current = null;\n userMsgForSaveRef.current = null;\n\n setMessages((prev) => [...prev, { role: 'assistant' as const, content: text }]);\n setResponse('');\n setIsLoading(false);\n setIsStreaming(false);\n isSendingRef.current = false;\n\n const rt = routingRef.current;\n const persistClientSide = rt.kind === 'cdecli' && rt.persistenceMode !== 'backend';\n\n if (sid && um && persistClientSide) {\n void saveMessages({\n sessionId: sid,\n userMessage: {\n content: um.content,\n attachments: um.attachments?.map((a) => ({\n id: a.id,\n name: a.name,\n type: a.type,\n mimeType: a.mimeType,\n size: a.size,\n url: a.url,\n })),\n },\n assistantMessage: {\n content: text,\n tokenCount: undefined,\n model: 'cdecli-serve',\n },\n }).catch((e) => console.error('[useChatStream] saveMessages (cdecli):', e));\n }\n },\n onError: (err: string) => {\n if (routingRef.current.kind !== 'cdecli') return;\n if (cdecliRoundHandledRef.current) return;\n const conv = cdecliFallbackConversationRef.current;\n const um = userMsgForSaveRef.current;\n const sid = saveSessionIdRef.current;\n if (conv?.length && um) {\n cdecliRoundHandledRef.current = true;\n console.warn(\n '[useChatStream] CDeCLI transport/stream error; falling back to direct chat API:',\n err,\n );\n setResponse('');\n setError(null);\n userMsgForSaveRef.current = um;\n saveSessionIdRef.current = sid;\n setIsLoading(true);\n setIsStreaming(true);\n isSendingRef.current = true;\n void executeGroqStream(conv, sid, um);\n return;\n }\n cdecliRoundHandledRef.current = false;\n setError(err);\n setResponse('');\n setIsLoading(false);\n setIsStreaming(false);\n isSendingRef.current = false;\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n },\n }),\n [saveMessages, executeGroqStream],\n );\n\n const { sendMessage: sendCdecliMessage } = useCdecliChannel(\n routing.kind === 'cdecli',\n isCdecliActive,\n routing.kind === 'cdecli' ? routing.accountId : 'default',\n streamChannelId,\n cdecliCallbacks,\n );\n\n useEffect(\n () => () => {\n if (revealIntervalRef.current) {\n clearInterval(revealIntervalRef.current);\n revealIntervalRef.current = null;\n }\n },\n [],\n );\n\n // Clear override once Apollo session id matches\n useEffect(() => {\n if (sessionId && cdecliStreamOverrideId && sessionId === cdecliStreamOverrideId) {\n setCdecliStreamOverrideId(undefined);\n }\n }, [sessionId, cdecliStreamOverrideId]);\n\n useEffect(() => {\n if (isSendingRef.current) return;\n\n if (!sessionId) {\n setMessages([]);\n setResponse('');\n return;\n }\n if (backendMessages && backendMessages.length > 0) {\n const formatted: ChatMessage[] = backendMessages.map((msg) => ({\n role: msg.role as 'user' | 'assistant',\n content: msg.content ?? '',\n metadata: msg.tokenCount\n ? {\n tokenUsage: {\n inputTokens: 0,\n outputTokens: msg.tokenCount,\n totalTokens: msg.tokenCount,\n },\n model: msg.model,\n }\n : undefined,\n }));\n setMessages(formatted);\n setResponse('');\n }\n }, [sessionId, backendMessages]);\n\n const sendMessage = useCallback(\n async (content: string, attachments?: MessageAttachment[], sessionIdOverride?: string | null) => {\n if (!content.trim()) return;\n\n const effectiveSessionId = sessionIdOverride !== undefined ? sessionIdOverride : sessionId;\n\n const userMessage: ChatMessage = {\n role: 'user',\n content: content.trim(),\n attachments,\n };\n\n isSendingRef.current = true;\n setMessages((prev) => [...prev, userMessage]);\n setResponse('');\n setIsLoading(true);\n setIsStreaming(true);\n setError(null);\n userMsgForSaveRef.current = userMessage;\n saveSessionIdRef.current = effectiveSessionId ?? null;\n cdecliRoundHandledRef.current = false;\n\n const rt = routingRef.current;\n if (rt.kind === 'cdecli') {\n if (!rt.channelConnected) {\n setError('CDeCLI is not connected yet. Check gateway status in the header.');\n setMessages((p) => p.slice(0, -1));\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n return;\n }\n if (!effectiveSessionId) {\n setError('Missing chat session id.');\n setMessages((p) => p.slice(0, -1));\n isSendingRef.current = false;\n setIsLoading(false);\n setIsStreaming(false);\n userMsgForSaveRef.current = null;\n saveSessionIdRef.current = null;\n return;\n }\n\n if (sessionIdOverride) {\n setCdecliStreamOverrideId(sessionIdOverride);\n await new Promise<void>((r) => setTimeout(r, 0));\n }\n\n cdecliFallbackConversationRef.current = [...messages, userMessage];\n\n const ok = await sendCdecliMessage(content.trim(), effectiveSessionId);\n if (!ok && !cdecliRoundHandledRef.current) {\n const conv = cdecliFallbackConversationRef.current;\n console.warn('[useChatStream] CDeCLI send returned false; falling back to direct chat API');\n cdecliRoundHandledRef.current = true;\n userMsgForSaveRef.current = userMessage;\n saveSessionIdRef.current = effectiveSessionId ?? null;\n void executeGroqStream(conv ?? [], effectiveSessionId ?? null, userMessage);\n return;\n }\n return;\n }\n\n await executeGroqStream([...messages, userMessage], effectiveSessionId ?? null, userMessage);\n },\n [messages, sessionId, sendCdecliMessage, executeGroqStream],\n );\n\n const cancel = useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort();\n }\n }, []);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n setResponse('');\n setError(null);\n }, []);\n\n return {\n messages,\n response,\n error,\n isLoading,\n isStreaming,\n hasMessages: messages.length > 0 || !!response,\n sendMessage,\n cancel,\n clearMessages,\n };\n}\n"],"names":["_a","_b","assistantMessage","sid","um"],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {useApolloClient}from'@apollo/client/index.js';import {useState,useRef,useCallback,useEffect}from'react';import {config}from'../config/env-config.js';import {OPENCLAW_CHAT_CONNECTION_QUERY}from'../graphql/agentGatewayDocuments.js';import {agentSessionManager}from'../services/agentSessionManager.js';import {GatewayClient}from'../services/gatewayClient.js';function isInstanceNotFoundError(error) {
|
|
2
|
+
if (!error) return false;
|
|
3
|
+
return error.includes("Instance not found") || error.includes("is the workspace running");
|
|
4
|
+
}
|
|
5
|
+
const MAX_CONNECT_RETRIES = 6;
|
|
6
|
+
const RETRY_BASE_DELAY_MS = 2e3;
|
|
7
|
+
const MAX_RETRY_DELAY_MS = 8e3;
|
|
8
|
+
function isTransientConnectionError(msg) {
|
|
9
|
+
return msg.includes("connection closed before handshake") || msg.includes("WebSocket connection error") || msg.includes("Failed to fetch") || msg.includes("Gateway GraphQL request failed: 5");
|
|
10
|
+
}
|
|
11
|
+
function useGatewayConnection(projectId, userId) {
|
|
12
|
+
var _a;
|
|
13
|
+
const client = useApolloClient();
|
|
14
|
+
const [status, setStatus] = useState(() => {
|
|
15
|
+
var _a2;
|
|
16
|
+
return ((_a2 = agentSessionManager.getClient()) == null ? void 0 : _a2.connected) ? "connected" : "disconnected";
|
|
17
|
+
});
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
const clientRef = useRef((_a = agentSessionManager.getClient()) != null ? _a : null);
|
|
20
|
+
const connectingRef = useRef(false);
|
|
21
|
+
const fetchGatewayConnection = useCallback(async (pid, uid) => {
|
|
22
|
+
var _a2;
|
|
23
|
+
const res = await client.query({
|
|
24
|
+
query: OPENCLAW_CHAT_CONNECTION_QUERY,
|
|
25
|
+
variables: {
|
|
26
|
+
projectId: pid,
|
|
27
|
+
userId: uid
|
|
28
|
+
},
|
|
29
|
+
fetchPolicy: "network-only"
|
|
30
|
+
});
|
|
31
|
+
if ((_a2 = res.errors) == null ? void 0 : _a2.length) throw new Error(res.errors[0].message);
|
|
32
|
+
const data = res.data;
|
|
33
|
+
const conn = data == null ? void 0 : data.openclawChatConnection;
|
|
34
|
+
if (!conn) throw new Error("No openclawChatConnection in response");
|
|
35
|
+
return conn;
|
|
36
|
+
}, [client]);
|
|
37
|
+
const disconnect = useCallback(() => {
|
|
38
|
+
if (clientRef.current) {
|
|
39
|
+
clientRef.current.close();
|
|
40
|
+
clientRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
agentSessionManager.detachGateway();
|
|
43
|
+
setStatus("disconnected");
|
|
44
|
+
setError(null);
|
|
45
|
+
connectingRef.current = false;
|
|
46
|
+
}, []);
|
|
47
|
+
const connect = useCallback(async () => {
|
|
48
|
+
var _a2;
|
|
49
|
+
if (connectingRef.current) {
|
|
50
|
+
console.log("[Gateway] Connection already in progress, skipping");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if ((_a2 = agentSessionManager.getClient()) == null ? void 0 : _a2.connected) {
|
|
54
|
+
clientRef.current = agentSessionManager.getClient();
|
|
55
|
+
setStatus("connected");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!projectId || !userId) {
|
|
59
|
+
console.warn("[Gateway] Cannot connect \u2014 projectId or userId is not available yet");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
connectingRef.current = true;
|
|
63
|
+
console.log("[Gateway] Connecting to gateway...");
|
|
64
|
+
if (clientRef.current) {
|
|
65
|
+
clientRef.current.close();
|
|
66
|
+
clientRef.current = null;
|
|
67
|
+
agentSessionManager.detachGateway();
|
|
68
|
+
}
|
|
69
|
+
setStatus("connecting");
|
|
70
|
+
setError(null);
|
|
71
|
+
let lastError = null;
|
|
72
|
+
for (let attempt = 1; attempt <= MAX_CONNECT_RETRIES; attempt++) {
|
|
73
|
+
try {
|
|
74
|
+
const conn = await fetchGatewayConnection(projectId, userId);
|
|
75
|
+
if (!(conn == null ? void 0 : conn.proxyToken)) {
|
|
76
|
+
throw new Error("No gateway connection available \u2014 is the workspace running?");
|
|
77
|
+
}
|
|
78
|
+
console.log(`[Gateway] Connecting (attempt ${attempt}/${MAX_CONNECT_RETRIES}): wsUrl=${conn.wsUrl} url=${conn.url} proxyWs=${config.AGENT_GATEWAY_WS_URL}`);
|
|
79
|
+
const wsUrl = config.AGENT_GATEWAY_WS_URL;
|
|
80
|
+
const wsClient = new GatewayClient(wsUrl, conn.token, conn.proxyToken);
|
|
81
|
+
clientRef.current = wsClient;
|
|
82
|
+
const hello = await wsClient.connect();
|
|
83
|
+
await agentSessionManager.attachGateway(wsClient, hello);
|
|
84
|
+
console.log("[Gateway] Connected successfully");
|
|
85
|
+
setStatus("connected");
|
|
86
|
+
connectingRef.current = false;
|
|
87
|
+
wsClient.on("disconnect", () => {
|
|
88
|
+
setStatus("disconnected");
|
|
89
|
+
connectingRef.current = false;
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
lastError = msg;
|
|
95
|
+
if (clientRef.current) {
|
|
96
|
+
clientRef.current.close();
|
|
97
|
+
clientRef.current = null;
|
|
98
|
+
}
|
|
99
|
+
if (!isTransientConnectionError(msg) || attempt >= MAX_CONNECT_RETRIES) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
const retryDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), MAX_RETRY_DELAY_MS);
|
|
103
|
+
console.warn(`[Gateway] Transient failure (attempt ${attempt}/${MAX_CONNECT_RETRIES}): ${msg} \u2014 retrying in ${retryDelay / 1e3}s`);
|
|
104
|
+
await new Promise((r) => {
|
|
105
|
+
setTimeout(r, retryDelay);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
console.error(`[Gateway] Connection failed after ${MAX_CONNECT_RETRIES} attempts: ${lastError} (projectId=${projectId}, userId=${userId}, wsUrl=${config.AGENT_GATEWAY_WS_URL})`);
|
|
110
|
+
setStatus("error");
|
|
111
|
+
setError(lastError);
|
|
112
|
+
connectingRef.current = false;
|
|
113
|
+
}, [projectId, userId, fetchGatewayConnection]);
|
|
114
|
+
useEffect(() => () => {
|
|
115
|
+
connectingRef.current = false;
|
|
116
|
+
}, []);
|
|
117
|
+
return {
|
|
118
|
+
status,
|
|
119
|
+
error,
|
|
120
|
+
connect,
|
|
121
|
+
disconnect
|
|
122
|
+
};
|
|
123
|
+
}export{isInstanceNotFoundError,useGatewayConnection};//# sourceMappingURL=useGatewayConnection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useGatewayConnection.js","sources":["../../src/hooks/useGatewayConnection.ts"],"sourcesContent":["/**\n * OpenClaw gateway WebSocket — same flow as account/browser, using Apollo\n * for `openclawChatConnection` so mobile gets the same auth as other operations.\n */\n\nimport { useApolloClient } from '@apollo/client';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { config } from '../config/env-config';\nimport { OPENCLAW_CHAT_CONNECTION_QUERY } from '../graphql/agentGatewayDocuments';\nimport { GatewayClient, agentSessionManager, resetGatewayApiKeyBridge } from '../services';\n\nexport type GatewayConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';\n\nexport interface UseGatewayConnectionResult {\n status: GatewayConnectionStatus;\n error: string | null;\n connect: () => Promise<void>;\n disconnect: () => void;\n}\n\nexport function isInstanceNotFoundError(error: string | null): boolean {\n if (!error) return false;\n return error.includes('Instance not found') || error.includes('is the workspace running');\n}\n\nconst MAX_CONNECT_RETRIES = 6;\nconst RETRY_BASE_DELAY_MS = 2000;\nconst MAX_RETRY_DELAY_MS = 8000;\n\nfunction isTransientConnectionError(msg: string): boolean {\n return (\n msg.includes('connection closed before handshake') ||\n msg.includes('WebSocket connection error') ||\n msg.includes('Failed to fetch') ||\n msg.includes('Gateway GraphQL request failed: 5')\n );\n}\n\nexport function useGatewayConnection(projectId: string | null, userId: string | null): UseGatewayConnectionResult {\n const client = useApolloClient();\n const [status, setStatus] = useState<GatewayConnectionStatus>(() =>\n agentSessionManager.getClient()?.connected ? 'connected' : 'disconnected',\n );\n const [error, setError] = useState<string | null>(null);\n const clientRef = useRef<GatewayClient | null>(agentSessionManager.getClient() ?? null);\n const connectingRef = useRef(false);\n\n const fetchGatewayConnection = useCallback(\n async (\n pid: string,\n uid: string,\n ): Promise<{ wsUrl: string; token: string; url: string; proxyToken: string; expiresAt: string }> => {\n const res = await client.query({\n query: OPENCLAW_CHAT_CONNECTION_QUERY,\n variables: { projectId: pid, userId: uid },\n fetchPolicy: 'network-only',\n });\n if (res.errors?.length) throw new Error(res.errors[0].message);\n const data = res.data as {\n openclawChatConnection: {\n wsUrl: string;\n token: string;\n url: string;\n proxyToken: string;\n expiresAt: string;\n };\n };\n const conn = data?.openclawChatConnection;\n if (!conn) throw new Error('No openclawChatConnection in response');\n return conn;\n },\n [client],\n );\n\n const disconnect = useCallback(() => {\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n }\n agentSessionManager.detachGateway();\n resetGatewayApiKeyBridge();\n setStatus('disconnected');\n setError(null);\n connectingRef.current = false;\n }, []);\n\n const connect = useCallback(async () => {\n if (connectingRef.current) {\n console.log('[Gateway] Connection already in progress, skipping');\n return;\n }\n if (agentSessionManager.getClient()?.connected) {\n clientRef.current = agentSessionManager.getClient();\n setStatus('connected');\n return;\n }\n if (!projectId || !userId) {\n console.warn('[Gateway] Cannot connect — projectId or userId is not available yet');\n return;\n }\n connectingRef.current = true;\n console.log('[Gateway] Connecting to gateway...');\n\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n agentSessionManager.detachGateway();\n }\n\n setStatus('connecting');\n setError(null);\n\n let lastError: string | null = null;\n\n for (let attempt = 1; attempt <= MAX_CONNECT_RETRIES; attempt++) {\n try {\n const conn = await fetchGatewayConnection(projectId, userId);\n if (!conn?.proxyToken) {\n throw new Error('No gateway connection available — is the workspace running?');\n }\n console.log(\n `[Gateway] Connecting (attempt ${attempt}/${MAX_CONNECT_RETRIES}): wsUrl=${conn.wsUrl} url=${conn.url} proxyWs=${config.AGENT_GATEWAY_WS_URL}`,\n );\n\n const wsUrl = config.AGENT_GATEWAY_WS_URL;\n const wsClient = new GatewayClient(wsUrl, conn.token, conn.proxyToken);\n clientRef.current = wsClient;\n\n const hello = await wsClient.connect();\n await agentSessionManager.attachGateway(wsClient, hello);\n\n console.log('[Gateway] Connected successfully');\n setStatus('connected');\n connectingRef.current = false;\n\n wsClient.on('disconnect', () => {\n setStatus('disconnected');\n connectingRef.current = false;\n });\n return;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n lastError = msg;\n\n if (clientRef.current) {\n clientRef.current.close();\n clientRef.current = null;\n }\n\n if (!isTransientConnectionError(msg) || attempt >= MAX_CONNECT_RETRIES) {\n break;\n }\n\n const retryDelay = Math.min(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), MAX_RETRY_DELAY_MS);\n console.warn(\n `[Gateway] Transient failure (attempt ${attempt}/${MAX_CONNECT_RETRIES}): ${msg} — retrying in ${retryDelay / 1000}s`,\n );\n await new Promise<void>((r) => {\n setTimeout(r, retryDelay);\n });\n }\n }\n\n console.error(\n `[Gateway] Connection failed after ${MAX_CONNECT_RETRIES} attempts: ${lastError} (projectId=${projectId}, userId=${userId}, wsUrl=${config.AGENT_GATEWAY_WS_URL})`,\n );\n setStatus('error');\n setError(lastError);\n connectingRef.current = false;\n }, [projectId, userId, fetchGatewayConnection]);\n\n useEffect(\n () => () => {\n connectingRef.current = false;\n },\n [],\n );\n\n return { status, error, connect, disconnect };\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {useMemo}from'react';import {BUILTIN_MOBILE_GATEWAYS}from'../config/constants.js';var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
3
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __spreadValues = (a, b) => {
|
|
7
|
+
for (var prop in b || (b = {}))
|
|
8
|
+
if (__hasOwnProp.call(b, prop))
|
|
9
|
+
__defNormalProp(a, prop, b[prop]);
|
|
10
|
+
if (__getOwnPropSymbols)
|
|
11
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
12
|
+
if (__propIsEnum.call(b, prop))
|
|
13
|
+
__defNormalProp(a, prop, b[prop]);
|
|
14
|
+
}
|
|
15
|
+
return a;
|
|
16
|
+
};
|
|
17
|
+
function useGatewayRegistry() {
|
|
18
|
+
const gateways = useMemo(() => __spreadValues({}, BUILTIN_MOBILE_GATEWAYS), []);
|
|
19
|
+
const gatewayList = useMemo(() => Object.entries(gateways).map(([id, entry]) => __spreadValues({
|
|
20
|
+
id
|
|
21
|
+
}, entry)).sort((a, b) => b.priority - a.priority), [gateways]);
|
|
22
|
+
return {
|
|
23
|
+
gateways,
|
|
24
|
+
gatewayList,
|
|
25
|
+
loading: false,
|
|
26
|
+
error: false
|
|
27
|
+
};
|
|
28
|
+
}export{useGatewayRegistry};//# sourceMappingURL=useGatewayRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useGatewayRegistry.js","sources":["../../src/hooks/useGatewayRegistry.ts"],"sourcesContent":["/** Mobile gateway registry. Uses built-ins to avoid settings shape issues in RN runtime. */\n\nimport { useMemo } from 'react';\nimport { BUILTIN_MOBILE_GATEWAYS, type GatewayEntry } from '../config/constants';\n\nexport interface UseGatewayRegistryReturn {\n gateways: Record<string, GatewayEntry>;\n gatewayList: Array<{ id: string } & GatewayEntry>;\n loading: boolean;\n error: boolean;\n}\n\nexport function useGatewayRegistry(): UseGatewayRegistryReturn {\n const gateways = useMemo(() => ({ ...BUILTIN_MOBILE_GATEWAYS }), []);\n\n const gatewayList = useMemo(\n () =>\n Object.entries(gateways)\n .map(([id, entry]) => ({ id, ...entry }))\n .sort((a, b) => b.priority - a.priority),\n [gateways],\n );\n\n return { gateways, gatewayList, loading: false, error: false };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAYO,SAAS,kBAA+C,GAAA;AAC7D,EAAA,MAAM,WAAW,OAAQ,CAAA,MAAO,cAC3B,CAAA,EAAA,EAAA,uBAAA,CAAA,EACD,EAAE,CAAA;AACN,EAAA,MAAM,WAAc,GAAA,OAAA,CAAQ,MAAM,MAAA,CAAO,OAAQ,CAAA,QAAQ,CAAE,CAAA,GAAA,CAAI,CAAC,CAAC,EAAI,EAAA,KAAK,CAAO,KAAA,cAAA,CAAA;AAAA,IAC/E;AAAA,GAAA,EACG,KACH,CAAA,CAAA,CAAE,IAAK,CAAA,CAAC,CAAG,EAAA,CAAA,KAAM,CAAE,CAAA,QAAA,GAAW,CAAE,CAAA,QAAQ,CAAG,EAAA,CAAC,QAAQ,CAAC,CAAA;AACvD,EAAO,OAAA;AAAA,IACL,QAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAS,EAAA,KAAA;AAAA,IACT,KAAO,EAAA;AAAA,GACT;AACF"}
|