@aomi-labs/react 0.0.0
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/dist/index.cjs +2092 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +2057 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2057 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/api/client.ts
|
|
22
|
+
function toQueryString(payload) {
|
|
23
|
+
const params = new URLSearchParams();
|
|
24
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
25
|
+
if (value === void 0 || value === null) continue;
|
|
26
|
+
params.set(key, String(value));
|
|
27
|
+
}
|
|
28
|
+
const qs = params.toString();
|
|
29
|
+
return qs ? `?${qs}` : "";
|
|
30
|
+
}
|
|
31
|
+
async function postState(backendUrl, path, payload) {
|
|
32
|
+
const query = toQueryString(payload);
|
|
33
|
+
const url = `${backendUrl}${path}${query}`;
|
|
34
|
+
console.log("\u{1F535} [postState] URL:", url);
|
|
35
|
+
console.log("\u{1F535} [postState] Payload:", payload);
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
method: "POST"
|
|
38
|
+
});
|
|
39
|
+
console.log("\u{1F535} [postState] Response status:", response.status);
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
console.error("\u{1F534} [postState] Error:", response.status, response.statusText);
|
|
42
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
console.log("\u{1F7E2} [postState] Success:", data);
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
var BackendApi = class {
|
|
49
|
+
constructor(backendUrl) {
|
|
50
|
+
this.backendUrl = backendUrl;
|
|
51
|
+
this.connectionStatus = false;
|
|
52
|
+
this.eventSource = null;
|
|
53
|
+
this.updatesEventSources = /* @__PURE__ */ new Map();
|
|
54
|
+
}
|
|
55
|
+
async fetchState(sessionId, options) {
|
|
56
|
+
console.log("\u{1F535} [fetchState] Called with sessionId:", sessionId);
|
|
57
|
+
const url = `${this.backendUrl}/api/state?session_id=${encodeURIComponent(sessionId)}`;
|
|
58
|
+
console.log("\u{1F535} [fetchState] URL:", url);
|
|
59
|
+
const response = await fetch(url, { signal: options == null ? void 0 : options.signal });
|
|
60
|
+
console.log("\u{1F535} [fetchState] Response status:", response.status, response.statusText);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
console.error("\u{1F534} [fetchState] Error:", response.status, response.statusText);
|
|
63
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
console.log("\u{1F7E2} [fetchState] Success:", data);
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
async postChatMessage(sessionId, message, publicKey) {
|
|
70
|
+
console.log("\u{1F535} [postChatMessage] Called with sessionId:", sessionId, "message:", message);
|
|
71
|
+
const result = await postState(this.backendUrl, "/api/chat", {
|
|
72
|
+
message,
|
|
73
|
+
session_id: sessionId,
|
|
74
|
+
public_key: publicKey
|
|
75
|
+
});
|
|
76
|
+
console.log("\u{1F7E2} [postChatMessage] Success:", result);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async postSystemMessage(sessionId, message) {
|
|
80
|
+
console.log("\u{1F535} [postSystemMessage] Called with sessionId:", sessionId, "message:", message);
|
|
81
|
+
const result = await postState(this.backendUrl, "/api/system", {
|
|
82
|
+
message,
|
|
83
|
+
session_id: sessionId
|
|
84
|
+
});
|
|
85
|
+
console.log("\u{1F7E2} [postSystemMessage] Success:", result);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
async postInterrupt(sessionId) {
|
|
89
|
+
console.log("\u{1F535} [postInterrupt] Called with sessionId:", sessionId);
|
|
90
|
+
const result = await postState(this.backendUrl, "/api/interrupt", {
|
|
91
|
+
session_id: sessionId
|
|
92
|
+
});
|
|
93
|
+
console.log("\u{1F7E2} [postInterrupt] Success:", result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
disconnectSSE() {
|
|
97
|
+
if (this.eventSource) {
|
|
98
|
+
this.eventSource.close();
|
|
99
|
+
this.eventSource = null;
|
|
100
|
+
}
|
|
101
|
+
this.setConnectionStatus(false);
|
|
102
|
+
}
|
|
103
|
+
setConnectionStatus(on) {
|
|
104
|
+
this.connectionStatus = on;
|
|
105
|
+
}
|
|
106
|
+
async connectSSE(sessionId, publicKey) {
|
|
107
|
+
this.disconnectSSE();
|
|
108
|
+
try {
|
|
109
|
+
const url = new URL(`${this.backendUrl}/api/chat/stream`);
|
|
110
|
+
url.searchParams.set("session_id", sessionId);
|
|
111
|
+
if (publicKey) {
|
|
112
|
+
url.searchParams.set("public_key", publicKey);
|
|
113
|
+
}
|
|
114
|
+
this.eventSource = new EventSource(url.toString());
|
|
115
|
+
this.eventSource.onopen = () => {
|
|
116
|
+
console.log("\u{1F310} SSE connection opened to:", url.toString());
|
|
117
|
+
this.setConnectionStatus(true);
|
|
118
|
+
};
|
|
119
|
+
this.eventSource.onmessage = (event) => {
|
|
120
|
+
try {
|
|
121
|
+
JSON.parse(event.data);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Failed to parse SSE data:", error);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
this.eventSource.onerror = (error) => {
|
|
127
|
+
console.error("SSE connection error:", error);
|
|
128
|
+
};
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error("Failed to establish SSE connection:", error);
|
|
131
|
+
this.handleConnectionError(sessionId, publicKey);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
handleConnectionError(sessionId, publicKey) {
|
|
135
|
+
this.setConnectionStatus(false);
|
|
136
|
+
let attempt = 0;
|
|
137
|
+
const total = 3;
|
|
138
|
+
if (attempt < total) {
|
|
139
|
+
attempt++;
|
|
140
|
+
console.log(`Attempting to reconnect (${attempt}/${total})...`);
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
this.connectSSE(sessionId, publicKey);
|
|
143
|
+
}, 100);
|
|
144
|
+
} else {
|
|
145
|
+
console.error("Max reconnection attempts reached");
|
|
146
|
+
this.setConnectionStatus(false);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
subscribeToUpdatesInternal(sessionId, onUpdate, onError, logLabel) {
|
|
150
|
+
const updatesUrl = new URL("/api/updates", this.backendUrl);
|
|
151
|
+
updatesUrl.searchParams.set("session_id", sessionId);
|
|
152
|
+
const updatesUrlString = updatesUrl.toString();
|
|
153
|
+
const existing = this.updatesEventSources.get(sessionId);
|
|
154
|
+
if (existing) {
|
|
155
|
+
existing.cleanup();
|
|
156
|
+
this.updatesEventSources.delete(sessionId);
|
|
157
|
+
}
|
|
158
|
+
const subscription = {
|
|
159
|
+
eventSource: null,
|
|
160
|
+
retries: 0,
|
|
161
|
+
retryTimer: null,
|
|
162
|
+
stopped: false,
|
|
163
|
+
cleanup: () => {
|
|
164
|
+
subscription.stopped = true;
|
|
165
|
+
if (subscription.retryTimer) {
|
|
166
|
+
clearTimeout(subscription.retryTimer);
|
|
167
|
+
subscription.retryTimer = null;
|
|
168
|
+
}
|
|
169
|
+
if (subscription.eventSource) {
|
|
170
|
+
subscription.eventSource.close();
|
|
171
|
+
subscription.eventSource = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const scheduleRetry = () => {
|
|
176
|
+
subscription.retries += 1;
|
|
177
|
+
const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
|
|
178
|
+
console.warn(
|
|
179
|
+
`\u{1F501} [${logLabel}] retrying in ${delayMs}ms (attempt ${subscription.retries})`,
|
|
180
|
+
{ sessionId }
|
|
181
|
+
);
|
|
182
|
+
subscription.retryTimer = setTimeout(() => {
|
|
183
|
+
open();
|
|
184
|
+
}, delayMs);
|
|
185
|
+
};
|
|
186
|
+
const open = () => {
|
|
187
|
+
if (subscription.stopped) return;
|
|
188
|
+
if (subscription.retryTimer) {
|
|
189
|
+
clearTimeout(subscription.retryTimer);
|
|
190
|
+
subscription.retryTimer = null;
|
|
191
|
+
}
|
|
192
|
+
if (subscription.eventSource) {
|
|
193
|
+
subscription.eventSource.close();
|
|
194
|
+
}
|
|
195
|
+
const updatesEventSource = new EventSource(updatesUrlString);
|
|
196
|
+
subscription.eventSource = updatesEventSource;
|
|
197
|
+
console.log(`\u{1F514} [updates] subscribed`, updatesUrlString);
|
|
198
|
+
updatesEventSource.onopen = () => {
|
|
199
|
+
subscription.retries = 0;
|
|
200
|
+
console.log("\u{1F514} [updates] open", updatesUrlString);
|
|
201
|
+
};
|
|
202
|
+
updatesEventSource.onmessage = (event) => {
|
|
203
|
+
try {
|
|
204
|
+
console.log("\u{1F514} [updates] message", { url: updatesUrlString, data: event.data });
|
|
205
|
+
const parsed = JSON.parse(event.data);
|
|
206
|
+
onUpdate(parsed);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error("Failed to parse system update SSE:", error);
|
|
209
|
+
onError == null ? void 0 : onError(error);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
updatesEventSource.onerror = (error) => {
|
|
213
|
+
console.error("System updates SSE error:", {
|
|
214
|
+
url: updatesUrlString,
|
|
215
|
+
readyState: updatesEventSource.readyState,
|
|
216
|
+
error
|
|
217
|
+
});
|
|
218
|
+
onError == null ? void 0 : onError(error);
|
|
219
|
+
if (subscription.stopped) return;
|
|
220
|
+
updatesEventSource.close();
|
|
221
|
+
scheduleRetry();
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
this.updatesEventSources.set(sessionId, subscription);
|
|
225
|
+
open();
|
|
226
|
+
return () => {
|
|
227
|
+
const current = this.updatesEventSources.get(sessionId);
|
|
228
|
+
if (current === subscription) {
|
|
229
|
+
current.cleanup();
|
|
230
|
+
this.updatesEventSources.delete(sessionId);
|
|
231
|
+
} else {
|
|
232
|
+
subscription.cleanup();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
subscribeToUpdates(sessionId, onUpdate, onError) {
|
|
237
|
+
return this.subscribeToUpdatesInternal(
|
|
238
|
+
sessionId,
|
|
239
|
+
onUpdate,
|
|
240
|
+
onError,
|
|
241
|
+
"subscribeToUpdates"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
async fetchThreads(publicKey) {
|
|
245
|
+
console.log("\u{1F535} [fetchThreads] Called with publicKey:", publicKey);
|
|
246
|
+
const url = `${this.backendUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
|
|
247
|
+
console.log("\u{1F535} [fetchThreads] URL:", url);
|
|
248
|
+
const response = await fetch(url);
|
|
249
|
+
console.log("\u{1F535} [fetchThreads] Response status:", response.status);
|
|
250
|
+
if (!response.ok) {
|
|
251
|
+
console.error("\u{1F534} [fetchThreads] Error:", response.status);
|
|
252
|
+
throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
|
|
253
|
+
}
|
|
254
|
+
const data = await response.json();
|
|
255
|
+
console.log("\u{1F7E2} [fetchThreads] Success:", data);
|
|
256
|
+
return data;
|
|
257
|
+
}
|
|
258
|
+
async createThread(publicKey, title) {
|
|
259
|
+
console.log("\u{1F535} [createThread] Called with publicKey:", publicKey, "title:", title);
|
|
260
|
+
const body = {};
|
|
261
|
+
if (publicKey) {
|
|
262
|
+
body.public_key = publicKey;
|
|
263
|
+
}
|
|
264
|
+
if (title) {
|
|
265
|
+
body.title = title;
|
|
266
|
+
}
|
|
267
|
+
console.log("\u{1F535} [createThread] Request body:", body);
|
|
268
|
+
const url = `${this.backendUrl}/api/sessions`;
|
|
269
|
+
console.log("\u{1F535} [createThread] URL:", url);
|
|
270
|
+
const response = await fetch(url, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { "Content-Type": "application/json" },
|
|
273
|
+
body: JSON.stringify(body)
|
|
274
|
+
});
|
|
275
|
+
console.log("\u{1F535} [createThread] Response status:", response.status);
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
console.error("\u{1F534} [createThread] Error:", response.status);
|
|
278
|
+
throw new Error(`Failed to create thread: HTTP ${response.status}`);
|
|
279
|
+
}
|
|
280
|
+
const data = await response.json();
|
|
281
|
+
console.log("\u{1F7E2} [createThread] Success:", data);
|
|
282
|
+
return data;
|
|
283
|
+
}
|
|
284
|
+
async archiveThread(sessionId) {
|
|
285
|
+
console.log("\u{1F535} [archiveThread] Called with sessionId:", sessionId);
|
|
286
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
|
|
287
|
+
console.log("\u{1F535} [archiveThread] URL:", url);
|
|
288
|
+
const response = await fetch(url, { method: "POST" });
|
|
289
|
+
console.log("\u{1F535} [archiveThread] Response status:", response.status);
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
console.error("\u{1F534} [archiveThread] Error:", response.status);
|
|
292
|
+
throw new Error(`Failed to archive thread: HTTP ${response.status}`);
|
|
293
|
+
}
|
|
294
|
+
console.log("\u{1F7E2} [archiveThread] Success");
|
|
295
|
+
}
|
|
296
|
+
async unarchiveThread(sessionId) {
|
|
297
|
+
console.log("\u{1F535} [unarchiveThread] Called with sessionId:", sessionId);
|
|
298
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
|
|
299
|
+
console.log("\u{1F535} [unarchiveThread] URL:", url);
|
|
300
|
+
const response = await fetch(url, { method: "POST" });
|
|
301
|
+
console.log("\u{1F535} [unarchiveThread] Response status:", response.status);
|
|
302
|
+
if (!response.ok) {
|
|
303
|
+
console.error("\u{1F534} [unarchiveThread] Error:", response.status);
|
|
304
|
+
throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
|
|
305
|
+
}
|
|
306
|
+
console.log("\u{1F7E2} [unarchiveThread] Success");
|
|
307
|
+
}
|
|
308
|
+
async deleteThread(sessionId) {
|
|
309
|
+
console.log("\u{1F535} [deleteThread] Called with sessionId:", sessionId);
|
|
310
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
311
|
+
console.log("\u{1F535} [deleteThread] URL:", url);
|
|
312
|
+
const response = await fetch(url, { method: "DELETE" });
|
|
313
|
+
console.log("\u{1F535} [deleteThread] Response status:", response.status);
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
console.error("\u{1F534} [deleteThread] Error:", response.status);
|
|
316
|
+
throw new Error(`Failed to delete thread: HTTP ${response.status}`);
|
|
317
|
+
}
|
|
318
|
+
console.log("\u{1F7E2} [deleteThread] Success");
|
|
319
|
+
}
|
|
320
|
+
async renameThread(sessionId, newTitle) {
|
|
321
|
+
console.log("\u{1F535} [renameThread] Called with sessionId:", sessionId, "newTitle:", newTitle);
|
|
322
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
323
|
+
console.log("\u{1F535} [renameThread] URL:", url);
|
|
324
|
+
const response = await fetch(url, {
|
|
325
|
+
method: "PATCH",
|
|
326
|
+
headers: { "Content-Type": "application/json" },
|
|
327
|
+
body: JSON.stringify({ title: newTitle })
|
|
328
|
+
});
|
|
329
|
+
console.log("\u{1F535} [renameThread] Response status:", response.status);
|
|
330
|
+
if (!response.ok) {
|
|
331
|
+
console.error("\u{1F534} [renameThread] Error:", response.status);
|
|
332
|
+
throw new Error(`Failed to rename thread: HTTP ${response.status}`);
|
|
333
|
+
}
|
|
334
|
+
console.log("\u{1F7E2} [renameThread] Success");
|
|
335
|
+
}
|
|
336
|
+
async fetchEventsAfter(sessionId, afterId = 0, limit = 100) {
|
|
337
|
+
const url = new URL("/api/events", this.backendUrl);
|
|
338
|
+
url.searchParams.set("session_id", sessionId);
|
|
339
|
+
if (afterId > 0) url.searchParams.set("after_id", String(afterId));
|
|
340
|
+
if (limit) url.searchParams.set("limit", String(limit));
|
|
341
|
+
console.log("\u{1F535} [fetchEventsAfter] URL:", url.toString());
|
|
342
|
+
const response = await fetch(url.toString());
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
throw new Error(`Failed to fetch events: HTTP ${response.status}`);
|
|
345
|
+
}
|
|
346
|
+
return await response.json();
|
|
347
|
+
}
|
|
348
|
+
subscribeToUpdatesWithNotification(sessionId, onUpdate, onError) {
|
|
349
|
+
return this.subscribeToUpdatesInternal(
|
|
350
|
+
sessionId,
|
|
351
|
+
onUpdate,
|
|
352
|
+
onError,
|
|
353
|
+
"subscribeToUpdatesWithNotification"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// src/runtime/aomi-runtime.tsx
|
|
359
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef4, useState as useState3 } from "react";
|
|
360
|
+
import {
|
|
361
|
+
AssistantRuntimeProvider,
|
|
362
|
+
useExternalStoreRuntime
|
|
363
|
+
} from "@assistant-ui/react";
|
|
364
|
+
|
|
365
|
+
// src/runtime/hooks.ts
|
|
366
|
+
import { createContext, useContext } from "react";
|
|
367
|
+
var RuntimeActionsContext = createContext(void 0);
|
|
368
|
+
var RuntimeActionsProvider = RuntimeActionsContext.Provider;
|
|
369
|
+
function useRuntimeActions() {
|
|
370
|
+
const context = useContext(RuntimeActionsContext);
|
|
371
|
+
if (!context) {
|
|
372
|
+
throw new Error("useRuntimeActions must be used within AomiRuntimeProvider");
|
|
373
|
+
}
|
|
374
|
+
return context;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/runtime/orchestrator.ts
|
|
378
|
+
import { useCallback, useEffect, useRef as useRef2, useState } from "react";
|
|
379
|
+
|
|
380
|
+
// src/state/thread-context.tsx
|
|
381
|
+
import { createContext as createContext2, useContext as useContext2, useMemo, useRef, useSyncExternalStore } from "react";
|
|
382
|
+
|
|
383
|
+
// src/state/thread-store.ts
|
|
384
|
+
var ThreadStore = class {
|
|
385
|
+
constructor(options) {
|
|
386
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
387
|
+
this.subscribe = (listener) => {
|
|
388
|
+
this.listeners.add(listener);
|
|
389
|
+
return () => {
|
|
390
|
+
this.listeners.delete(listener);
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
this.getSnapshot = () => this.snapshot;
|
|
394
|
+
this.setCurrentThreadId = (threadId) => {
|
|
395
|
+
this.ensureThreadExists(threadId);
|
|
396
|
+
this.updateState({ currentThreadId: threadId });
|
|
397
|
+
};
|
|
398
|
+
this.bumpThreadViewKey = () => {
|
|
399
|
+
this.updateState({ threadViewKey: this.state.threadViewKey + 1 });
|
|
400
|
+
};
|
|
401
|
+
this.setThreadCnt = (updater) => {
|
|
402
|
+
const nextCnt = this.resolveStateAction(updater, this.state.threadCnt);
|
|
403
|
+
this.updateState({ threadCnt: nextCnt });
|
|
404
|
+
};
|
|
405
|
+
this.setThreads = (updater) => {
|
|
406
|
+
const nextThreads = this.resolveStateAction(updater, this.state.threads);
|
|
407
|
+
this.updateState({ threads: new Map(nextThreads) });
|
|
408
|
+
};
|
|
409
|
+
this.setThreadMetadata = (updater) => {
|
|
410
|
+
const nextMetadata = this.resolveStateAction(updater, this.state.threadMetadata);
|
|
411
|
+
this.updateState({ threadMetadata: new Map(nextMetadata) });
|
|
412
|
+
};
|
|
413
|
+
this.setThreadMessages = (threadId, messages) => {
|
|
414
|
+
this.ensureThreadExists(threadId);
|
|
415
|
+
const nextThreads = new Map(this.state.threads);
|
|
416
|
+
nextThreads.set(threadId, messages);
|
|
417
|
+
this.updateState({ threads: nextThreads });
|
|
418
|
+
};
|
|
419
|
+
this.getThreadMessages = (threadId) => {
|
|
420
|
+
var _a;
|
|
421
|
+
return (_a = this.state.threads.get(threadId)) != null ? _a : [];
|
|
422
|
+
};
|
|
423
|
+
this.getThreadMetadata = (threadId) => {
|
|
424
|
+
return this.state.threadMetadata.get(threadId);
|
|
425
|
+
};
|
|
426
|
+
this.updateThreadMetadata = (threadId, updates) => {
|
|
427
|
+
const existing = this.state.threadMetadata.get(threadId);
|
|
428
|
+
if (!existing) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const nextMetadata = new Map(this.state.threadMetadata);
|
|
432
|
+
nextMetadata.set(threadId, __spreadValues(__spreadValues({}, existing), updates));
|
|
433
|
+
this.updateState({ threadMetadata: nextMetadata });
|
|
434
|
+
};
|
|
435
|
+
var _a;
|
|
436
|
+
const initialThreadId = (_a = options == null ? void 0 : options.initialThreadId) != null ? _a : crypto.randomUUID();
|
|
437
|
+
this.state = {
|
|
438
|
+
currentThreadId: initialThreadId,
|
|
439
|
+
threadViewKey: 0,
|
|
440
|
+
threadCnt: 1,
|
|
441
|
+
threads: /* @__PURE__ */ new Map([[initialThreadId, []]]),
|
|
442
|
+
threadMetadata: /* @__PURE__ */ new Map([
|
|
443
|
+
[
|
|
444
|
+
initialThreadId,
|
|
445
|
+
{
|
|
446
|
+
title: "New Chat",
|
|
447
|
+
status: "pending",
|
|
448
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
])
|
|
452
|
+
};
|
|
453
|
+
this.snapshot = this.buildSnapshot();
|
|
454
|
+
}
|
|
455
|
+
emit() {
|
|
456
|
+
for (const listener of this.listeners) {
|
|
457
|
+
listener();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
resolveStateAction(updater, current) {
|
|
461
|
+
return typeof updater === "function" ? updater(current) : updater;
|
|
462
|
+
}
|
|
463
|
+
ensureThreadExists(threadId) {
|
|
464
|
+
if (!this.state.threadMetadata.has(threadId)) {
|
|
465
|
+
const nextMetadata = new Map(this.state.threadMetadata);
|
|
466
|
+
nextMetadata.set(threadId, {
|
|
467
|
+
title: "New Chat",
|
|
468
|
+
status: "regular",
|
|
469
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
470
|
+
});
|
|
471
|
+
this.state = __spreadProps(__spreadValues({}, this.state), { threadMetadata: nextMetadata });
|
|
472
|
+
}
|
|
473
|
+
if (!this.state.threads.has(threadId)) {
|
|
474
|
+
const nextThreads = new Map(this.state.threads);
|
|
475
|
+
nextThreads.set(threadId, []);
|
|
476
|
+
this.state = __spreadProps(__spreadValues({}, this.state), { threads: nextThreads });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
updateState(partial) {
|
|
480
|
+
this.state = __spreadValues(__spreadValues({}, this.state), partial);
|
|
481
|
+
this.snapshot = this.buildSnapshot();
|
|
482
|
+
this.emit();
|
|
483
|
+
}
|
|
484
|
+
buildSnapshot() {
|
|
485
|
+
return {
|
|
486
|
+
currentThreadId: this.state.currentThreadId,
|
|
487
|
+
setCurrentThreadId: this.setCurrentThreadId,
|
|
488
|
+
threadViewKey: this.state.threadViewKey,
|
|
489
|
+
bumpThreadViewKey: this.bumpThreadViewKey,
|
|
490
|
+
threads: this.state.threads,
|
|
491
|
+
setThreads: this.setThreads,
|
|
492
|
+
threadMetadata: this.state.threadMetadata,
|
|
493
|
+
setThreadMetadata: this.setThreadMetadata,
|
|
494
|
+
threadCnt: this.state.threadCnt,
|
|
495
|
+
setThreadCnt: this.setThreadCnt,
|
|
496
|
+
getThreadMessages: this.getThreadMessages,
|
|
497
|
+
setThreadMessages: this.setThreadMessages,
|
|
498
|
+
getThreadMetadata: this.getThreadMetadata,
|
|
499
|
+
updateThreadMetadata: this.updateThreadMetadata
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// src/state/thread-context.tsx
|
|
505
|
+
import { jsx } from "react/jsx-runtime";
|
|
506
|
+
var ThreadContextState = createContext2(null);
|
|
507
|
+
function useThreadContext() {
|
|
508
|
+
const context = useContext2(ThreadContextState);
|
|
509
|
+
if (!context) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
"useThreadContext must be used within ThreadContextProvider. Wrap your app with <ThreadContextProvider>...</ThreadContextProvider>"
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return context;
|
|
515
|
+
}
|
|
516
|
+
function ThreadContextProvider({
|
|
517
|
+
children,
|
|
518
|
+
initialThreadId
|
|
519
|
+
}) {
|
|
520
|
+
const storeRef = useRef(null);
|
|
521
|
+
if (!storeRef.current) {
|
|
522
|
+
storeRef.current = new ThreadStore({ initialThreadId });
|
|
523
|
+
}
|
|
524
|
+
const store = storeRef.current;
|
|
525
|
+
const value = useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
526
|
+
return /* @__PURE__ */ jsx(ThreadContextState.Provider, { value, children });
|
|
527
|
+
}
|
|
528
|
+
function useCurrentThreadMessages() {
|
|
529
|
+
const { currentThreadId, getThreadMessages } = useThreadContext();
|
|
530
|
+
return useMemo(() => getThreadMessages(currentThreadId), [currentThreadId, getThreadMessages]);
|
|
531
|
+
}
|
|
532
|
+
function useCurrentThreadMetadata() {
|
|
533
|
+
const { currentThreadId, getThreadMetadata } = useThreadContext();
|
|
534
|
+
return useMemo(() => getThreadMetadata(currentThreadId), [currentThreadId, getThreadMetadata]);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/utils/conversion.ts
|
|
538
|
+
function toInboundMessage(msg) {
|
|
539
|
+
var _a;
|
|
540
|
+
if (msg.sender === "system") return null;
|
|
541
|
+
const content = [];
|
|
542
|
+
const role = msg.sender === "user" ? "user" : "assistant";
|
|
543
|
+
if (msg.content) {
|
|
544
|
+
content.push({ type: "text", text: msg.content });
|
|
545
|
+
}
|
|
546
|
+
const [topic, toolContent] = (_a = parseToolStream(msg.tool_stream)) != null ? _a : [];
|
|
547
|
+
if (topic && toolContent) {
|
|
548
|
+
content.push({
|
|
549
|
+
type: "tool-call",
|
|
550
|
+
toolCallId: `tool_${Date.now()}`,
|
|
551
|
+
toolName: topic,
|
|
552
|
+
args: void 0,
|
|
553
|
+
result: (() => {
|
|
554
|
+
try {
|
|
555
|
+
return JSON.parse(toolContent);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
return { args: toolContent };
|
|
558
|
+
}
|
|
559
|
+
})()
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
const threadMessage = __spreadValues({
|
|
563
|
+
role,
|
|
564
|
+
content: content.length > 0 ? content : [{ type: "text", text: "" }]
|
|
565
|
+
}, msg.timestamp && { createdAt: new Date(msg.timestamp) });
|
|
566
|
+
return threadMessage;
|
|
567
|
+
}
|
|
568
|
+
function toInboundSystem(msg) {
|
|
569
|
+
var _a;
|
|
570
|
+
const [topic] = (_a = parseToolStream(msg.tool_stream)) != null ? _a : [];
|
|
571
|
+
const messageText = topic || msg.content || "";
|
|
572
|
+
const timestamp = parseTimestamp(msg.timestamp);
|
|
573
|
+
if (!messageText.trim()) return null;
|
|
574
|
+
return __spreadValues({
|
|
575
|
+
role: "system",
|
|
576
|
+
content: [{ type: "text", text: messageText }]
|
|
577
|
+
}, timestamp && { createdAt: timestamp });
|
|
578
|
+
}
|
|
579
|
+
function parseTimestamp(timestamp) {
|
|
580
|
+
if (!timestamp) return void 0;
|
|
581
|
+
const parsed = new Date(timestamp);
|
|
582
|
+
return Number.isNaN(parsed.valueOf()) ? void 0 : parsed;
|
|
583
|
+
}
|
|
584
|
+
function parseToolStream(toolStream) {
|
|
585
|
+
if (!toolStream) return null;
|
|
586
|
+
if (Array.isArray(toolStream) && toolStream.length === 2) {
|
|
587
|
+
const [topic, content] = toolStream;
|
|
588
|
+
return [String(topic), content];
|
|
589
|
+
}
|
|
590
|
+
if (typeof toolStream === "object") {
|
|
591
|
+
const topic = toolStream.topic;
|
|
592
|
+
const content = toolStream.content;
|
|
593
|
+
return topic ? [String(topic), String(content)] : null;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/runtime/utils.ts
|
|
599
|
+
var isTempThreadId = (id) => id.startsWith("temp-");
|
|
600
|
+
var parseTimestamp2 = (value) => {
|
|
601
|
+
if (value === void 0 || value === null) return 0;
|
|
602
|
+
if (typeof value === "number") {
|
|
603
|
+
return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
|
|
604
|
+
}
|
|
605
|
+
const numeric = Number(value);
|
|
606
|
+
if (!Number.isNaN(numeric)) {
|
|
607
|
+
return numeric < 1e12 ? numeric * 1e3 : numeric;
|
|
608
|
+
}
|
|
609
|
+
const ts = Date.parse(value);
|
|
610
|
+
return Number.isNaN(ts) ? 0 : ts;
|
|
611
|
+
};
|
|
612
|
+
var isPlaceholderTitle = (title) => {
|
|
613
|
+
var _a;
|
|
614
|
+
const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
|
|
615
|
+
return !normalized || normalized.startsWith("#[");
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// src/runtime/backend-state.ts
|
|
619
|
+
function createBakendState() {
|
|
620
|
+
return {
|
|
621
|
+
tempToBackendId: /* @__PURE__ */ new Map(),
|
|
622
|
+
skipInitialFetch: /* @__PURE__ */ new Set(),
|
|
623
|
+
pendingChat: /* @__PURE__ */ new Map(),
|
|
624
|
+
pendingSystem: /* @__PURE__ */ new Map(),
|
|
625
|
+
runningThreads: /* @__PURE__ */ new Set(),
|
|
626
|
+
creatingThreadId: null,
|
|
627
|
+
createThreadPromise: null
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function resolveThreadId(state, threadId) {
|
|
631
|
+
var _a;
|
|
632
|
+
return (_a = state.tempToBackendId.get(threadId)) != null ? _a : threadId;
|
|
633
|
+
}
|
|
634
|
+
function isThreadReady(state, threadId) {
|
|
635
|
+
if (!isTempThreadId(threadId)) return true;
|
|
636
|
+
return state.tempToBackendId.has(threadId);
|
|
637
|
+
}
|
|
638
|
+
function setBackendMapping(state, tempId, backendId) {
|
|
639
|
+
state.tempToBackendId.set(tempId, backendId);
|
|
640
|
+
}
|
|
641
|
+
function findTempIdForBackendId(state, backendId) {
|
|
642
|
+
for (const [tempId, id] of state.tempToBackendId.entries()) {
|
|
643
|
+
if (id === backendId) return tempId;
|
|
644
|
+
}
|
|
645
|
+
return void 0;
|
|
646
|
+
}
|
|
647
|
+
function markSkipInitialFetch(state, threadId) {
|
|
648
|
+
state.skipInitialFetch.add(threadId);
|
|
649
|
+
}
|
|
650
|
+
function shouldSkipInitialFetch(state, threadId) {
|
|
651
|
+
return state.skipInitialFetch.has(threadId);
|
|
652
|
+
}
|
|
653
|
+
function clearSkipInitialFetch(state, threadId) {
|
|
654
|
+
state.skipInitialFetch.delete(threadId);
|
|
655
|
+
}
|
|
656
|
+
function setThreadRunning(state, threadId, running) {
|
|
657
|
+
if (running) {
|
|
658
|
+
state.runningThreads.add(threadId);
|
|
659
|
+
} else {
|
|
660
|
+
state.runningThreads.delete(threadId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
function isThreadRunning(state, threadId) {
|
|
664
|
+
return state.runningThreads.has(threadId);
|
|
665
|
+
}
|
|
666
|
+
function enqueuePendingChat(state, threadId, text) {
|
|
667
|
+
var _a;
|
|
668
|
+
const existing = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
|
|
669
|
+
state.pendingChat.set(threadId, [...existing, text]);
|
|
670
|
+
}
|
|
671
|
+
function dequeuePendingChat(state, threadId) {
|
|
672
|
+
var _a;
|
|
673
|
+
const pending = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
|
|
674
|
+
state.pendingChat.delete(threadId);
|
|
675
|
+
return pending;
|
|
676
|
+
}
|
|
677
|
+
function hasPendingChat(state, threadId) {
|
|
678
|
+
var _a, _b;
|
|
679
|
+
return ((_b = (_a = state.pendingChat.get(threadId)) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
|
|
680
|
+
}
|
|
681
|
+
function enqueuePendingSystem(state, threadId, text) {
|
|
682
|
+
var _a;
|
|
683
|
+
const existing = (_a = state.pendingSystem.get(threadId)) != null ? _a : [];
|
|
684
|
+
state.pendingSystem.set(threadId, [...existing, text]);
|
|
685
|
+
}
|
|
686
|
+
function dequeuePendingSystem(state, threadId) {
|
|
687
|
+
var _a;
|
|
688
|
+
const pending = (_a = state.pendingSystem.get(threadId)) != null ? _a : [];
|
|
689
|
+
state.pendingSystem.delete(threadId);
|
|
690
|
+
return pending;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/runtime/message-controller.ts
|
|
694
|
+
var MessageController = class {
|
|
695
|
+
constructor(config) {
|
|
696
|
+
this.config = config;
|
|
697
|
+
}
|
|
698
|
+
inbound(threadId, msgs) {
|
|
699
|
+
const backendState = this.config.backendStateRef.current;
|
|
700
|
+
if (!msgs) return;
|
|
701
|
+
if (hasPendingChat(backendState, threadId)) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const threadMessages = [];
|
|
705
|
+
for (const msg of msgs) {
|
|
706
|
+
if (msg.sender === "system") {
|
|
707
|
+
const systemMessage = toInboundSystem(msg);
|
|
708
|
+
if (systemMessage) {
|
|
709
|
+
threadMessages.push(systemMessage);
|
|
710
|
+
}
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
const threadMessage = toInboundMessage(msg);
|
|
714
|
+
if (threadMessage) {
|
|
715
|
+
threadMessages.push(threadMessage);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
|
|
719
|
+
}
|
|
720
|
+
async outbound(message, threadId) {
|
|
721
|
+
var _a, _b;
|
|
722
|
+
const backendState = this.config.backendStateRef.current;
|
|
723
|
+
const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
724
|
+
if (!text) return;
|
|
725
|
+
const threadState = this.getThreadContextApi();
|
|
726
|
+
const existingMessages = threadState.getThreadMessages(threadId);
|
|
727
|
+
const userMessage = {
|
|
728
|
+
role: "user",
|
|
729
|
+
content: [{ type: "text", text }],
|
|
730
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
731
|
+
};
|
|
732
|
+
threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
|
|
733
|
+
threadState.updateThreadMetadata(threadId, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
734
|
+
if (!isThreadReady(backendState, threadId)) {
|
|
735
|
+
this.markRunning(threadId, true);
|
|
736
|
+
enqueuePendingChat(backendState, threadId, text);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
740
|
+
const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
|
|
741
|
+
try {
|
|
742
|
+
this.markRunning(threadId, true);
|
|
743
|
+
if (publicKey) {
|
|
744
|
+
await this.config.backendApiRef.current.postChatMessage(
|
|
745
|
+
backendThreadId,
|
|
746
|
+
text,
|
|
747
|
+
publicKey
|
|
748
|
+
);
|
|
749
|
+
} else {
|
|
750
|
+
await this.config.backendApiRef.current.postChatMessage(backendThreadId, text);
|
|
751
|
+
}
|
|
752
|
+
await this.flushPendingSystem(threadId);
|
|
753
|
+
this.config.polling.start(threadId);
|
|
754
|
+
} catch (error) {
|
|
755
|
+
console.error("Failed to send message:", error);
|
|
756
|
+
this.markRunning(threadId, false);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async outboundSystem(threadId, text) {
|
|
760
|
+
const backendState = this.config.backendStateRef.current;
|
|
761
|
+
if (!isThreadReady(backendState, threadId)) return;
|
|
762
|
+
const threadMessages = this.getThreadContextApi().getThreadMessages(threadId);
|
|
763
|
+
const hasUserMessages = threadMessages.some((msg) => msg.role === "user");
|
|
764
|
+
if (!hasUserMessages) {
|
|
765
|
+
enqueuePendingSystem(backendState, threadId, text);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
await this.outboundSystemInner(threadId, text);
|
|
769
|
+
}
|
|
770
|
+
async outboundSystemInner(threadId, text) {
|
|
771
|
+
const backendState = this.config.backendStateRef.current;
|
|
772
|
+
const threadState = this.getThreadContextApi();
|
|
773
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
774
|
+
this.markRunning(threadId, true);
|
|
775
|
+
try {
|
|
776
|
+
const response = await this.config.backendApiRef.current.postSystemMessage(backendThreadId, text);
|
|
777
|
+
if (response.res) {
|
|
778
|
+
const systemMessage = toInboundSystem(response.res);
|
|
779
|
+
if (systemMessage) {
|
|
780
|
+
const updatedMessages = [...threadState.getThreadMessages(threadId), systemMessage];
|
|
781
|
+
threadState.setThreadMessages(threadId, updatedMessages);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
await this.flushPendingSystem(threadId);
|
|
785
|
+
this.config.polling.start(threadId);
|
|
786
|
+
} catch (error) {
|
|
787
|
+
console.error("Failed to send system message:", error);
|
|
788
|
+
this.markRunning(threadId, false);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async flushPendingSystem(threadId) {
|
|
792
|
+
const backendState = this.config.backendStateRef.current;
|
|
793
|
+
const pending = dequeuePendingSystem(backendState, threadId);
|
|
794
|
+
if (!pending.length) return;
|
|
795
|
+
for (const pendingMessage of pending) {
|
|
796
|
+
await this.outboundSystemInner(threadId, pendingMessage);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async flushPendingChat(threadId) {
|
|
800
|
+
var _a, _b;
|
|
801
|
+
const backendState = this.config.backendStateRef.current;
|
|
802
|
+
const pending = dequeuePendingChat(backendState, threadId);
|
|
803
|
+
if (!pending.length) return;
|
|
804
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
805
|
+
const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
|
|
806
|
+
for (const text of pending) {
|
|
807
|
+
try {
|
|
808
|
+
if (publicKey) {
|
|
809
|
+
await this.config.backendApiRef.current.postChatMessage(
|
|
810
|
+
backendThreadId,
|
|
811
|
+
text,
|
|
812
|
+
publicKey
|
|
813
|
+
);
|
|
814
|
+
} else {
|
|
815
|
+
await this.config.backendApiRef.current.postChatMessage(backendThreadId, text);
|
|
816
|
+
}
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.error("Failed to send queued message:", error);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
this.config.polling.start(threadId);
|
|
822
|
+
}
|
|
823
|
+
async cancel(threadId) {
|
|
824
|
+
const backendState = this.config.backendStateRef.current;
|
|
825
|
+
if (!isThreadReady(backendState, threadId)) return;
|
|
826
|
+
this.config.polling.stop(threadId);
|
|
827
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
828
|
+
try {
|
|
829
|
+
await this.config.backendApiRef.current.postInterrupt(backendThreadId);
|
|
830
|
+
this.markRunning(threadId, false);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
console.error("Failed to cancel:", error);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
markRunning(threadId, running) {
|
|
836
|
+
var _a, _b;
|
|
837
|
+
setThreadRunning(this.config.backendStateRef.current, threadId, running);
|
|
838
|
+
if (this.config.threadContextRef.current.currentThreadId === threadId) {
|
|
839
|
+
(_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
getThreadContextApi() {
|
|
843
|
+
const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
|
|
844
|
+
return { getThreadMessages, setThreadMessages, updateThreadMetadata };
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
// src/runtime/polling-controller.ts
|
|
849
|
+
var PollingController = class {
|
|
850
|
+
constructor(config) {
|
|
851
|
+
this.config = config;
|
|
852
|
+
this.intervals = /* @__PURE__ */ new Map();
|
|
853
|
+
var _a;
|
|
854
|
+
this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
|
|
855
|
+
this.handleSystemEvents = config.handleSystemEvents;
|
|
856
|
+
}
|
|
857
|
+
setSystemEventsHandler(handler) {
|
|
858
|
+
this.handleSystemEvents = handler;
|
|
859
|
+
}
|
|
860
|
+
start(threadId) {
|
|
861
|
+
const backendState = this.config.backendStateRef.current;
|
|
862
|
+
if (!isThreadReady(backendState, threadId)) return;
|
|
863
|
+
if (this.intervals.has(threadId)) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
867
|
+
setThreadRunning(backendState, threadId, true);
|
|
868
|
+
const tick = async () => {
|
|
869
|
+
if (!this.intervals.has(threadId)) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
console.log("\u{1F535} [PollingController] Fetching state for threadId:", threadId);
|
|
874
|
+
const state = await this.config.backendApiRef.current.fetchState(backendThreadId);
|
|
875
|
+
if (!this.intervals.has(threadId)) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
this.handleState(threadId, state);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
console.error("Polling error:", error);
|
|
881
|
+
this.stop(threadId);
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
const intervalId = setInterval(tick, this.intervalMs);
|
|
885
|
+
this.intervals.set(threadId, intervalId);
|
|
886
|
+
if (this.config.onStart) {
|
|
887
|
+
this.config.onStart(threadId);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
stop(threadId) {
|
|
891
|
+
var _a, _b;
|
|
892
|
+
const intervalId = this.intervals.get(threadId);
|
|
893
|
+
if (intervalId) {
|
|
894
|
+
clearInterval(intervalId);
|
|
895
|
+
this.intervals.delete(threadId);
|
|
896
|
+
}
|
|
897
|
+
setThreadRunning(this.config.backendStateRef.current, threadId, false);
|
|
898
|
+
(_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
|
|
899
|
+
}
|
|
900
|
+
isPolling(threadId) {
|
|
901
|
+
return this.intervals.has(threadId);
|
|
902
|
+
}
|
|
903
|
+
stopAll() {
|
|
904
|
+
for (const threadId of this.intervals.keys()) {
|
|
905
|
+
this.stop(threadId);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
handleState(threadId, state) {
|
|
909
|
+
if (state.session_exists === false) {
|
|
910
|
+
this.stop(threadId);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
const backendState = this.config.backendStateRef.current;
|
|
914
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
915
|
+
if (this.handleSystemEvents && state.system_events) {
|
|
916
|
+
this.handleSystemEvents(backendThreadId, threadId, state.system_events);
|
|
917
|
+
}
|
|
918
|
+
this.config.applyMessages(threadId, state.messages);
|
|
919
|
+
if (!state.is_processing) {
|
|
920
|
+
this.stop(threadId);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/runtime/orchestrator.ts
|
|
926
|
+
function useRuntimeOrchestrator(backendUrl, options) {
|
|
927
|
+
const threadContext = useThreadContext();
|
|
928
|
+
const threadContextRef = useRef2(threadContext);
|
|
929
|
+
threadContextRef.current = threadContext;
|
|
930
|
+
const backendApiRef = useRef2(new BackendApi(backendUrl));
|
|
931
|
+
const backendStateRef = useRef2(createBakendState());
|
|
932
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
933
|
+
const messageControllerRef = useRef2(null);
|
|
934
|
+
const pollingRef = useRef2(null);
|
|
935
|
+
const systemEventsHandlerRef = useRef2(null);
|
|
936
|
+
const inFlightRef = useRef2(/* @__PURE__ */ new Map());
|
|
937
|
+
if (!pollingRef.current) {
|
|
938
|
+
pollingRef.current = new PollingController({
|
|
939
|
+
backendApiRef,
|
|
940
|
+
backendStateRef,
|
|
941
|
+
applyMessages: (threadId, msgs) => {
|
|
942
|
+
var _a;
|
|
943
|
+
(_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
|
|
944
|
+
},
|
|
945
|
+
onStart: (threadId) => {
|
|
946
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
947
|
+
setIsRunning(true);
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
onStop: (threadId) => {
|
|
951
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
952
|
+
setIsRunning(false);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
if (!messageControllerRef.current) {
|
|
958
|
+
messageControllerRef.current = new MessageController({
|
|
959
|
+
backendApiRef,
|
|
960
|
+
backendStateRef,
|
|
961
|
+
threadContextRef,
|
|
962
|
+
polling: pollingRef.current,
|
|
963
|
+
setGlobalIsRunning: setIsRunning,
|
|
964
|
+
getPublicKey: options == null ? void 0 : options.getPublicKey
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
const setSystemEventsHandler = useCallback((handler) => {
|
|
968
|
+
var _a;
|
|
969
|
+
systemEventsHandlerRef.current = handler;
|
|
970
|
+
(_a = pollingRef.current) == null ? void 0 : _a.setSystemEventsHandler(handler != null ? handler : void 0);
|
|
971
|
+
}, []);
|
|
972
|
+
useEffect(() => {
|
|
973
|
+
return () => {
|
|
974
|
+
for (const controller of inFlightRef.current.values()) {
|
|
975
|
+
controller.abort();
|
|
976
|
+
}
|
|
977
|
+
inFlightRef.current.clear();
|
|
978
|
+
};
|
|
979
|
+
}, []);
|
|
980
|
+
const ensureInitialState = useCallback(
|
|
981
|
+
async (threadId) => {
|
|
982
|
+
var _a, _b;
|
|
983
|
+
const backendState = backendStateRef.current;
|
|
984
|
+
if (shouldSkipInitialFetch(backendState, threadId)) {
|
|
985
|
+
clearSkipInitialFetch(backendState, threadId);
|
|
986
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
987
|
+
setIsRunning(false);
|
|
988
|
+
}
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
if (!isThreadReady(backendState, threadId)) {
|
|
992
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
993
|
+
setIsRunning(false);
|
|
994
|
+
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (inFlightRef.current.has(threadId)) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1001
|
+
const controller = new AbortController();
|
|
1002
|
+
inFlightRef.current.set(threadId, controller);
|
|
1003
|
+
try {
|
|
1004
|
+
console.log("\u{1F535} [Orchestrator] Fetching initial state for threadId:", threadId);
|
|
1005
|
+
const state = await backendApiRef.current.fetchState(backendThreadId, {
|
|
1006
|
+
signal: controller.signal
|
|
1007
|
+
});
|
|
1008
|
+
(_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, state.messages);
|
|
1009
|
+
if (systemEventsHandlerRef.current && state.system_events) {
|
|
1010
|
+
systemEventsHandlerRef.current(backendThreadId, threadId, state.system_events);
|
|
1011
|
+
}
|
|
1012
|
+
if (state.is_processing) {
|
|
1013
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1014
|
+
setIsRunning(true);
|
|
1015
|
+
}
|
|
1016
|
+
(_b = pollingRef.current) == null ? void 0 : _b.start(threadId);
|
|
1017
|
+
} else {
|
|
1018
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1019
|
+
setIsRunning(false);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
if (controller.signal.aborted) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
console.error("Failed to fetch initial state:", error);
|
|
1027
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1028
|
+
setIsRunning(false);
|
|
1029
|
+
}
|
|
1030
|
+
} finally {
|
|
1031
|
+
if (inFlightRef.current.get(threadId) === controller) {
|
|
1032
|
+
inFlightRef.current.delete(threadId);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
[backendApiRef, backendStateRef, pollingRef, messageControllerRef, setIsRunning]
|
|
1037
|
+
);
|
|
1038
|
+
return {
|
|
1039
|
+
backendStateRef,
|
|
1040
|
+
polling: pollingRef.current,
|
|
1041
|
+
messageController: messageControllerRef.current,
|
|
1042
|
+
isRunning,
|
|
1043
|
+
setIsRunning,
|
|
1044
|
+
ensureInitialState,
|
|
1045
|
+
setSystemEventsHandler,
|
|
1046
|
+
backendApiRef
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/lib/notification-context.tsx
|
|
1051
|
+
import {
|
|
1052
|
+
createContext as createContext3,
|
|
1053
|
+
useCallback as useCallback2,
|
|
1054
|
+
useContext as useContext3,
|
|
1055
|
+
useState as useState2
|
|
1056
|
+
} from "react";
|
|
1057
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1058
|
+
var NotificationContext = createContext3(
|
|
1059
|
+
void 0
|
|
1060
|
+
);
|
|
1061
|
+
function useNotification() {
|
|
1062
|
+
const context = useContext3(NotificationContext);
|
|
1063
|
+
if (!context) {
|
|
1064
|
+
throw new Error("useNotification must be used within NotificationProvider");
|
|
1065
|
+
}
|
|
1066
|
+
return context;
|
|
1067
|
+
}
|
|
1068
|
+
function NotificationProvider({ children }) {
|
|
1069
|
+
const [notifications, setNotifications] = useState2([]);
|
|
1070
|
+
const showNotification = useCallback2(
|
|
1071
|
+
(notification) => {
|
|
1072
|
+
var _a, _b;
|
|
1073
|
+
const id = `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1074
|
+
const newNotification = __spreadProps(__spreadValues({}, notification), {
|
|
1075
|
+
id,
|
|
1076
|
+
duration: (_a = notification.duration) != null ? _a : 5e3
|
|
1077
|
+
});
|
|
1078
|
+
setNotifications((prev) => [newNotification, ...prev]);
|
|
1079
|
+
const duration = (_b = newNotification.duration) != null ? _b : 5e3;
|
|
1080
|
+
if (duration > 0) {
|
|
1081
|
+
setTimeout(() => {
|
|
1082
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
1083
|
+
}, duration);
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
[]
|
|
1087
|
+
);
|
|
1088
|
+
const dismissNotification = useCallback2((id) => {
|
|
1089
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
1090
|
+
}, []);
|
|
1091
|
+
return /* @__PURE__ */ jsx2(
|
|
1092
|
+
NotificationContext.Provider,
|
|
1093
|
+
{
|
|
1094
|
+
value: { showNotification, notifications, dismissNotification },
|
|
1095
|
+
children
|
|
1096
|
+
}
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// src/utils/wallet.ts
|
|
1101
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1102
|
+
var getNetworkName = (chainId) => {
|
|
1103
|
+
if (chainId === void 0) return "";
|
|
1104
|
+
const id = typeof chainId === "string" ? Number(chainId) : chainId;
|
|
1105
|
+
switch (id) {
|
|
1106
|
+
case 1:
|
|
1107
|
+
return "ethereum";
|
|
1108
|
+
case 137:
|
|
1109
|
+
return "polygon";
|
|
1110
|
+
case 42161:
|
|
1111
|
+
return "arbitrum";
|
|
1112
|
+
case 8453:
|
|
1113
|
+
return "base";
|
|
1114
|
+
case 10:
|
|
1115
|
+
return "optimism";
|
|
1116
|
+
case 11155111:
|
|
1117
|
+
return "sepolia";
|
|
1118
|
+
case 1337:
|
|
1119
|
+
case 31337:
|
|
1120
|
+
return "testnet";
|
|
1121
|
+
case 59140:
|
|
1122
|
+
return "linea-sepolia";
|
|
1123
|
+
case 59144:
|
|
1124
|
+
return "linea";
|
|
1125
|
+
default:
|
|
1126
|
+
return "testnet";
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
|
|
1130
|
+
function normalizeWalletError(error) {
|
|
1131
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1132
|
+
const e = error;
|
|
1133
|
+
const cause = (_a = e == null ? void 0 : e.cause) != null ? _a : null;
|
|
1134
|
+
const code = (_b = typeof (e == null ? void 0 : e.code) === "number" ? e.code : void 0) != null ? _b : typeof (cause == null ? void 0 : cause.code) === "number" ? cause.code : void 0;
|
|
1135
|
+
const name = (_c = typeof (e == null ? void 0 : e.name) === "string" ? e.name : void 0) != null ? _c : typeof (cause == null ? void 0 : cause.name) === "string" ? cause.name : void 0;
|
|
1136
|
+
const msg = (_g = (_f = (_e = (_d = typeof (e == null ? void 0 : e.shortMessage) === "string" ? e.shortMessage : void 0) != null ? _d : typeof (cause == null ? void 0 : cause.shortMessage) === "string" ? cause.shortMessage : void 0) != null ? _e : typeof (e == null ? void 0 : e.message) === "string" ? e.message : void 0) != null ? _f : typeof (cause == null ? void 0 : cause.message) === "string" ? cause.message : void 0) != null ? _g : "Unknown wallet error";
|
|
1137
|
+
const rejected = code === 4001 || name === "UserRejectedRequestError" || name === "RejectedRequestError" || /user rejected|rejected the request|denied|request rejected|canceled|cancelled/i.test(
|
|
1138
|
+
msg
|
|
1139
|
+
);
|
|
1140
|
+
return { rejected, message: msg };
|
|
1141
|
+
}
|
|
1142
|
+
function toHexQuantity(value) {
|
|
1143
|
+
const trimmed = value.trim();
|
|
1144
|
+
const asBigInt = BigInt(trimmed);
|
|
1145
|
+
return `0x${asBigInt.toString(16)}`;
|
|
1146
|
+
}
|
|
1147
|
+
async function pickInjectedProvider(publicKey) {
|
|
1148
|
+
const ethereum = globalThis.ethereum;
|
|
1149
|
+
if (!(ethereum == null ? void 0 : ethereum.request)) return void 0;
|
|
1150
|
+
const candidates = Array.isArray(ethereum.providers) ? ethereum.providers.filter(
|
|
1151
|
+
(p) => !!(p == null ? void 0 : p.request)
|
|
1152
|
+
) : [ethereum];
|
|
1153
|
+
const target = publicKey == null ? void 0 : publicKey.toLowerCase();
|
|
1154
|
+
if (target) {
|
|
1155
|
+
for (const candidate of candidates) {
|
|
1156
|
+
try {
|
|
1157
|
+
const accounts = await candidate.request({
|
|
1158
|
+
method: "eth_accounts"
|
|
1159
|
+
});
|
|
1160
|
+
const list = Array.isArray(accounts) ? accounts.map((a) => String(a).toLowerCase()) : [];
|
|
1161
|
+
if (list.includes(target)) return candidate;
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return candidates[0];
|
|
1167
|
+
}
|
|
1168
|
+
function WalletSystemMessageEmitter({
|
|
1169
|
+
wallet
|
|
1170
|
+
}) {
|
|
1171
|
+
const { sendSystemMessage } = useRuntimeActions();
|
|
1172
|
+
const lastWalletRef = useRef3({ isConnected: false });
|
|
1173
|
+
useEffect2(() => {
|
|
1174
|
+
const prev = lastWalletRef.current;
|
|
1175
|
+
const { address, chainId, isConnected } = wallet;
|
|
1176
|
+
const normalizedAddress = address == null ? void 0 : address.toLowerCase();
|
|
1177
|
+
if (isConnected && normalizedAddress && chainId && (!prev.isConnected || prev.address !== normalizedAddress)) {
|
|
1178
|
+
const networkName = getNetworkName(chainId);
|
|
1179
|
+
const message = `User connected wallet with address ${normalizedAddress} on ${networkName} network (Chain ID: ${chainId}). Ready to help with transactions.`;
|
|
1180
|
+
console.log(message);
|
|
1181
|
+
void sendSystemMessage(message);
|
|
1182
|
+
lastWalletRef.current = {
|
|
1183
|
+
isConnected: true,
|
|
1184
|
+
address: normalizedAddress,
|
|
1185
|
+
chainId
|
|
1186
|
+
};
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (!isConnected && prev.isConnected) {
|
|
1190
|
+
void sendSystemMessage("Wallet disconnected by user.");
|
|
1191
|
+
console.log("Wallet disconnected by user.");
|
|
1192
|
+
lastWalletRef.current = { isConnected: false };
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (isConnected && normalizedAddress && chainId && prev.isConnected && prev.address === normalizedAddress && prev.chainId !== chainId) {
|
|
1196
|
+
const networkName = getNetworkName(chainId);
|
|
1197
|
+
const message = `User switched wallet to ${networkName} network (Chain ID: ${chainId}).`;
|
|
1198
|
+
console.log(message);
|
|
1199
|
+
void sendSystemMessage(message);
|
|
1200
|
+
lastWalletRef.current = {
|
|
1201
|
+
isConnected: true,
|
|
1202
|
+
address: normalizedAddress,
|
|
1203
|
+
chainId
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
}, [wallet, sendSystemMessage]);
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/runtime/wallet-handler.ts
|
|
1211
|
+
var WalletHandler = class {
|
|
1212
|
+
constructor(config) {
|
|
1213
|
+
this.config = config;
|
|
1214
|
+
this.handledRequests = /* @__PURE__ */ new Set();
|
|
1215
|
+
this.queue = [];
|
|
1216
|
+
this.inFlight = false;
|
|
1217
|
+
}
|
|
1218
|
+
handleRequest(sessionId, threadId, request) {
|
|
1219
|
+
if (this.config.getCurrentThreadId() !== threadId) return;
|
|
1220
|
+
const description = request.description || request.topic || "Wallet transaction requested";
|
|
1221
|
+
this.config.showNotification({
|
|
1222
|
+
type: "notice",
|
|
1223
|
+
iconType: "wallet",
|
|
1224
|
+
title: "Transaction Request",
|
|
1225
|
+
message: description
|
|
1226
|
+
});
|
|
1227
|
+
this.enqueue(sessionId, threadId, request);
|
|
1228
|
+
void this.drain();
|
|
1229
|
+
}
|
|
1230
|
+
enqueue(sessionId, threadId, request) {
|
|
1231
|
+
var _a;
|
|
1232
|
+
const key = `${sessionId}:${(_a = request.timestamp) != null ? _a : JSON.stringify(request)}`;
|
|
1233
|
+
if (this.handledRequests.has(key)) return;
|
|
1234
|
+
this.handledRequests.add(key);
|
|
1235
|
+
this.queue.push({ sessionId, threadId, request });
|
|
1236
|
+
}
|
|
1237
|
+
async drain() {
|
|
1238
|
+
var _a, _b, _c;
|
|
1239
|
+
if (this.inFlight) return;
|
|
1240
|
+
const next = this.queue.shift();
|
|
1241
|
+
if (!next) return;
|
|
1242
|
+
this.inFlight = true;
|
|
1243
|
+
try {
|
|
1244
|
+
if (this.config.onWalletTxRequest) {
|
|
1245
|
+
const txHash2 = await this.config.onWalletTxRequest(next.request, {
|
|
1246
|
+
sessionId: next.sessionId,
|
|
1247
|
+
threadId: next.threadId,
|
|
1248
|
+
publicKey: this.config.publicKey
|
|
1249
|
+
});
|
|
1250
|
+
this.config.showNotification({
|
|
1251
|
+
type: "success",
|
|
1252
|
+
iconType: "transaction",
|
|
1253
|
+
title: "Transaction Sent",
|
|
1254
|
+
message: `Hash: ${txHash2}`
|
|
1255
|
+
});
|
|
1256
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1257
|
+
next.sessionId,
|
|
1258
|
+
`Transaction sent: ${txHash2}`
|
|
1259
|
+
);
|
|
1260
|
+
await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const activeProvider = await pickInjectedProvider(this.config.publicKey);
|
|
1264
|
+
if (!(activeProvider == null ? void 0 : activeProvider.request)) {
|
|
1265
|
+
this.config.showNotification({
|
|
1266
|
+
type: "error",
|
|
1267
|
+
iconType: "wallet",
|
|
1268
|
+
title: "Wallet Not Found",
|
|
1269
|
+
message: "No wallet provider found (window.ethereum missing)."
|
|
1270
|
+
});
|
|
1271
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1272
|
+
next.sessionId,
|
|
1273
|
+
"No wallet provider found (window.ethereum missing)."
|
|
1274
|
+
);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const accounts = await activeProvider.request({
|
|
1278
|
+
method: "eth_accounts"
|
|
1279
|
+
});
|
|
1280
|
+
const addresses = Array.isArray(accounts) ? accounts.map(String) : [];
|
|
1281
|
+
const from = this.config.publicKey || addresses[0];
|
|
1282
|
+
if (!from) {
|
|
1283
|
+
await activeProvider.request({ method: "eth_requestAccounts" });
|
|
1284
|
+
}
|
|
1285
|
+
const fromAddress = this.config.publicKey || await activeProvider.request({ method: "eth_accounts" });
|
|
1286
|
+
const resolvedFrom = this.config.publicKey || (Array.isArray(fromAddress) ? String((_a = fromAddress[0]) != null ? _a : "") : "");
|
|
1287
|
+
if (!resolvedFrom) {
|
|
1288
|
+
this.config.showNotification({
|
|
1289
|
+
type: "error",
|
|
1290
|
+
iconType: "wallet",
|
|
1291
|
+
title: "Wallet Not Connected",
|
|
1292
|
+
message: "Please connect a wallet to sign the requested transaction."
|
|
1293
|
+
});
|
|
1294
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1295
|
+
next.sessionId,
|
|
1296
|
+
"Wallet is not connected; please connect a wallet to sign the requested transaction."
|
|
1297
|
+
);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const gas = (_c = (_b = next.request.gas) != null ? _b : next.request.gas_limit) != null ? _c : void 0;
|
|
1301
|
+
let valueHex;
|
|
1302
|
+
let gasHex;
|
|
1303
|
+
try {
|
|
1304
|
+
valueHex = toHexQuantity(next.request.value);
|
|
1305
|
+
if (gas) gasHex = toHexQuantity(gas);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
this.config.showNotification({
|
|
1308
|
+
type: "error",
|
|
1309
|
+
iconType: "transaction",
|
|
1310
|
+
title: "Invalid Transaction",
|
|
1311
|
+
message: error.message
|
|
1312
|
+
});
|
|
1313
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1314
|
+
next.sessionId,
|
|
1315
|
+
`Invalid wallet transaction request payload: ${error.message}`
|
|
1316
|
+
);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const txParams = __spreadValues({
|
|
1320
|
+
from: resolvedFrom,
|
|
1321
|
+
to: next.request.to,
|
|
1322
|
+
value: valueHex,
|
|
1323
|
+
data: next.request.data
|
|
1324
|
+
}, gasHex ? { gas: gasHex } : {});
|
|
1325
|
+
const txHash = await activeProvider.request({
|
|
1326
|
+
method: "eth_sendTransaction",
|
|
1327
|
+
params: [txParams]
|
|
1328
|
+
});
|
|
1329
|
+
this.config.showNotification({
|
|
1330
|
+
type: "success",
|
|
1331
|
+
title: "Transaction sent",
|
|
1332
|
+
message: `Transaction hash: ${txHash}`
|
|
1333
|
+
});
|
|
1334
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1335
|
+
next.sessionId,
|
|
1336
|
+
`Transaction sent: ${txHash}`
|
|
1337
|
+
);
|
|
1338
|
+
await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
const normalized = normalizeWalletError(error);
|
|
1341
|
+
const final = normalized.rejected ? "Transaction rejected by user." : `Transaction failed: ${normalized.message}`;
|
|
1342
|
+
this.config.showNotification({
|
|
1343
|
+
type: normalized.rejected ? "notice" : "error",
|
|
1344
|
+
iconType: normalized.rejected ? "transaction" : "error",
|
|
1345
|
+
title: normalized.rejected ? "Transaction Rejected" : "Transaction Failed",
|
|
1346
|
+
message: normalized.rejected ? "Transaction was rejected by user." : normalized.message
|
|
1347
|
+
});
|
|
1348
|
+
try {
|
|
1349
|
+
await this.config.backendApiRef.current.postSystemMessage(
|
|
1350
|
+
next.sessionId,
|
|
1351
|
+
final
|
|
1352
|
+
);
|
|
1353
|
+
await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
|
|
1354
|
+
} catch (postError) {
|
|
1355
|
+
console.error("Failed to report wallet tx result to backend:", postError);
|
|
1356
|
+
}
|
|
1357
|
+
} finally {
|
|
1358
|
+
this.inFlight = false;
|
|
1359
|
+
void this.drain();
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
async refreshThreadIfCurrent(sessionId, threadId) {
|
|
1363
|
+
if (this.config.getCurrentThreadId() !== threadId) return;
|
|
1364
|
+
try {
|
|
1365
|
+
const state = await this.config.backendApiRef.current.fetchState(sessionId);
|
|
1366
|
+
this.config.applySessionMessagesToThread(threadId, state.messages);
|
|
1367
|
+
} catch (refreshError) {
|
|
1368
|
+
console.error("Failed to refresh state after wallet tx:", refreshError);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
// src/runtime/event-controller.ts
|
|
1374
|
+
var EventController = class {
|
|
1375
|
+
constructor(config) {
|
|
1376
|
+
this.config = config;
|
|
1377
|
+
this.lastEventIdBySession = /* @__PURE__ */ new Map();
|
|
1378
|
+
this.eventsInFlight = /* @__PURE__ */ new Set();
|
|
1379
|
+
this.updateSubscriptions = /* @__PURE__ */ new Map();
|
|
1380
|
+
this.subscribableSessionId = null;
|
|
1381
|
+
}
|
|
1382
|
+
setSubscribableSessionId(sessionId) {
|
|
1383
|
+
console.log("\u{1F514} [updates] setSubscribableSessionId", { sessionId });
|
|
1384
|
+
this.subscribableSessionId = sessionId;
|
|
1385
|
+
this.syncSubscriptions();
|
|
1386
|
+
}
|
|
1387
|
+
syncSubscriptions() {
|
|
1388
|
+
console.log("\u{1F514} [updates] syncSubscriptions", {
|
|
1389
|
+
subscribableSessionId: this.subscribableSessionId,
|
|
1390
|
+
active: Array.from(this.updateSubscriptions.keys())
|
|
1391
|
+
});
|
|
1392
|
+
const nextSessions = /* @__PURE__ */ new Set();
|
|
1393
|
+
if (this.subscribableSessionId) {
|
|
1394
|
+
nextSessions.add(this.subscribableSessionId);
|
|
1395
|
+
}
|
|
1396
|
+
for (const sessionId of this.updateSubscriptions.keys()) {
|
|
1397
|
+
if (!nextSessions.has(sessionId)) {
|
|
1398
|
+
this.removeSubscription(sessionId);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
for (const sessionId of nextSessions) {
|
|
1402
|
+
this.ensureSubscription(sessionId);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
ensureSubscription(sessionId) {
|
|
1406
|
+
if (this.updateSubscriptions.has(sessionId)) return;
|
|
1407
|
+
console.log("\u{1F514} [updates] ensureSubscription", { sessionId });
|
|
1408
|
+
const unsubscribe = this.config.backendApiRef.current.subscribeToUpdates(
|
|
1409
|
+
sessionId,
|
|
1410
|
+
(update) => {
|
|
1411
|
+
console.log("\u{1F514} [updates] event_available", {
|
|
1412
|
+
sessionId: update.session_id,
|
|
1413
|
+
eventId: update.event_id,
|
|
1414
|
+
eventType: update.event_type
|
|
1415
|
+
});
|
|
1416
|
+
if (update.type !== "event_available") return;
|
|
1417
|
+
void this.drainEvents(update.session_id);
|
|
1418
|
+
},
|
|
1419
|
+
(error) => {
|
|
1420
|
+
console.error("Failed to handle system update SSE:", error);
|
|
1421
|
+
}
|
|
1422
|
+
);
|
|
1423
|
+
this.updateSubscriptions.set(sessionId, unsubscribe);
|
|
1424
|
+
}
|
|
1425
|
+
removeSubscription(sessionId) {
|
|
1426
|
+
const unsubscribe = this.updateSubscriptions.get(sessionId);
|
|
1427
|
+
if (!unsubscribe) return;
|
|
1428
|
+
unsubscribe();
|
|
1429
|
+
this.updateSubscriptions.delete(sessionId);
|
|
1430
|
+
}
|
|
1431
|
+
async drainEvents(sessionId) {
|
|
1432
|
+
var _a;
|
|
1433
|
+
if (this.eventsInFlight.has(sessionId)) {
|
|
1434
|
+
console.log("\u23F3 [events] drain already in flight", { sessionId });
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
this.eventsInFlight.add(sessionId);
|
|
1438
|
+
try {
|
|
1439
|
+
let afterId = (_a = this.lastEventIdBySession.get(sessionId)) != null ? _a : 0;
|
|
1440
|
+
console.log("\u{1F4E5} [events] start drain", { sessionId, afterId });
|
|
1441
|
+
for (; ; ) {
|
|
1442
|
+
const requestAfterId = afterId;
|
|
1443
|
+
const events = await this.config.backendApiRef.current.fetchEventsAfter(
|
|
1444
|
+
sessionId,
|
|
1445
|
+
requestAfterId,
|
|
1446
|
+
200
|
|
1447
|
+
);
|
|
1448
|
+
console.log("\u{1F4E5} [events] fetched batch", {
|
|
1449
|
+
sessionId,
|
|
1450
|
+
afterId: requestAfterId,
|
|
1451
|
+
count: events.length
|
|
1452
|
+
});
|
|
1453
|
+
if (!events.length) break;
|
|
1454
|
+
for (const event of events) {
|
|
1455
|
+
const eventId = typeof event.event_id === "number" ? event.event_id : Number(event.event_id);
|
|
1456
|
+
if (Number.isFinite(eventId)) afterId = Math.max(afterId, eventId);
|
|
1457
|
+
if (event.type === "title_changed" && typeof event.new_title === "string") {
|
|
1458
|
+
console.log("\u{1F3F7}\uFE0F [events] title_changed", {
|
|
1459
|
+
sessionId,
|
|
1460
|
+
eventId,
|
|
1461
|
+
newTitle: event.new_title
|
|
1462
|
+
});
|
|
1463
|
+
this.applyTitleChanged(sessionId, event.new_title);
|
|
1464
|
+
}
|
|
1465
|
+
if (event.type === "wallet_tx_request") {
|
|
1466
|
+
const payload = event.payload;
|
|
1467
|
+
if (payload && typeof payload === "object") {
|
|
1468
|
+
const req = payload;
|
|
1469
|
+
if (typeof req.to === "string" && typeof req.value === "string" && typeof req.data === "string") {
|
|
1470
|
+
const threadId = findTempIdForBackendId(
|
|
1471
|
+
this.config.backendStateRef.current,
|
|
1472
|
+
sessionId
|
|
1473
|
+
) || sessionId;
|
|
1474
|
+
this.config.handleWalletTxRequest(
|
|
1475
|
+
sessionId,
|
|
1476
|
+
threadId,
|
|
1477
|
+
req
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
if (events.length < 200) break;
|
|
1484
|
+
}
|
|
1485
|
+
console.log("\u{1F4E5} [events] drain complete", { sessionId, afterId });
|
|
1486
|
+
this.lastEventIdBySession.set(sessionId, afterId);
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
console.error("Failed to fetch async events:", error);
|
|
1489
|
+
} finally {
|
|
1490
|
+
this.eventsInFlight.delete(sessionId);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
applyTitleChanged(sessionId, newTitle) {
|
|
1494
|
+
const backendState = this.config.backendStateRef.current;
|
|
1495
|
+
const tempId = findTempIdForBackendId(backendState, sessionId);
|
|
1496
|
+
const threadIdToUpdate = tempId || sessionId;
|
|
1497
|
+
console.log("\u{1F3F7}\uFE0F [title] applying", {
|
|
1498
|
+
sessionId,
|
|
1499
|
+
threadId: threadIdToUpdate,
|
|
1500
|
+
newTitle
|
|
1501
|
+
});
|
|
1502
|
+
this.config.setThreadMetadata((prev) => {
|
|
1503
|
+
var _a;
|
|
1504
|
+
const next = new Map(prev);
|
|
1505
|
+
const existing = next.get(threadIdToUpdate);
|
|
1506
|
+
const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
|
|
1507
|
+
const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
|
|
1508
|
+
next.set(threadIdToUpdate, {
|
|
1509
|
+
title: normalizedTitle,
|
|
1510
|
+
status: nextStatus,
|
|
1511
|
+
lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString()
|
|
1512
|
+
});
|
|
1513
|
+
return next;
|
|
1514
|
+
});
|
|
1515
|
+
if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === threadIdToUpdate) {
|
|
1516
|
+
backendState.creatingThreadId = null;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
handleBackendSystemEvents(sessionId, threadId, rawEvents) {
|
|
1520
|
+
if (!(rawEvents == null ? void 0 : rawEvents.length)) return;
|
|
1521
|
+
for (const raw of rawEvents) {
|
|
1522
|
+
const parsed = this.parseBackendSystemEvent(raw);
|
|
1523
|
+
if (!parsed) continue;
|
|
1524
|
+
if ("InlineDisplay" in parsed) {
|
|
1525
|
+
const payload = parsed.InlineDisplay;
|
|
1526
|
+
if (!payload || typeof payload !== "object") continue;
|
|
1527
|
+
const type = payload.type;
|
|
1528
|
+
if (type !== "wallet_tx_request") continue;
|
|
1529
|
+
const requestValue = payload.payload;
|
|
1530
|
+
if (!requestValue || typeof requestValue !== "object") continue;
|
|
1531
|
+
const req = requestValue;
|
|
1532
|
+
if (typeof req.to !== "string" || typeof req.value !== "string" || typeof req.data !== "string") {
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
this.config.handleWalletTxRequest(
|
|
1536
|
+
sessionId,
|
|
1537
|
+
threadId,
|
|
1538
|
+
req
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
if ("SystemError" in parsed) {
|
|
1542
|
+
this.config.showNotification({
|
|
1543
|
+
type: "error",
|
|
1544
|
+
iconType: "error",
|
|
1545
|
+
title: "Error",
|
|
1546
|
+
message: parsed.SystemError
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
if ("SystemNotice" in parsed) {
|
|
1550
|
+
this.config.showNotification({
|
|
1551
|
+
type: "notice",
|
|
1552
|
+
iconType: "notice",
|
|
1553
|
+
title: "Notice",
|
|
1554
|
+
message: parsed.SystemNotice
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
parseBackendSystemEvent(value) {
|
|
1560
|
+
if (!value || typeof value !== "object") return null;
|
|
1561
|
+
const entries = Object.entries(value);
|
|
1562
|
+
if (entries.length !== 1) return null;
|
|
1563
|
+
const [key, payload] = entries[0];
|
|
1564
|
+
switch (key) {
|
|
1565
|
+
case "InlineDisplay":
|
|
1566
|
+
return { InlineDisplay: payload };
|
|
1567
|
+
case "SystemNotice":
|
|
1568
|
+
return {
|
|
1569
|
+
SystemNotice: typeof payload === "string" ? payload : String(payload)
|
|
1570
|
+
};
|
|
1571
|
+
case "SystemError":
|
|
1572
|
+
return {
|
|
1573
|
+
SystemError: typeof payload === "string" ? payload : String(payload)
|
|
1574
|
+
};
|
|
1575
|
+
case "AsyncUpdate":
|
|
1576
|
+
return { AsyncUpdate: payload };
|
|
1577
|
+
default:
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
cleanup() {
|
|
1582
|
+
for (const unsubscribe of this.updateSubscriptions.values()) {
|
|
1583
|
+
unsubscribe();
|
|
1584
|
+
}
|
|
1585
|
+
this.updateSubscriptions.clear();
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
// src/runtime/aomi-runtime.tsx
|
|
1590
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1591
|
+
var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
|
|
1592
|
+
const tsA = parseTimestamp2(metaA.lastActiveAt);
|
|
1593
|
+
const tsB = parseTimestamp2(metaB.lastActiveAt);
|
|
1594
|
+
return tsB - tsA;
|
|
1595
|
+
};
|
|
1596
|
+
function buildThreadLists(threadMetadata) {
|
|
1597
|
+
const entries = Array.from(threadMetadata.entries()).filter(
|
|
1598
|
+
([, meta]) => !isPlaceholderTitle(meta.title)
|
|
1599
|
+
);
|
|
1600
|
+
const regularThreads = entries.filter(([, meta]) => meta.status === "regular").sort(sortByLastActiveDesc).map(([id, meta]) => ({
|
|
1601
|
+
id,
|
|
1602
|
+
title: meta.title || "New Chat",
|
|
1603
|
+
status: "regular"
|
|
1604
|
+
}));
|
|
1605
|
+
const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(([id, meta]) => ({
|
|
1606
|
+
id,
|
|
1607
|
+
title: meta.title || "New Chat",
|
|
1608
|
+
status: "archived"
|
|
1609
|
+
}));
|
|
1610
|
+
return { regularThreads, archivedThreads };
|
|
1611
|
+
}
|
|
1612
|
+
function AomiRuntimeProvider({
|
|
1613
|
+
children,
|
|
1614
|
+
backendUrl = "http://localhost:8080",
|
|
1615
|
+
publicKey,
|
|
1616
|
+
onWalletTxRequest
|
|
1617
|
+
}) {
|
|
1618
|
+
const threadContext = useThreadContext();
|
|
1619
|
+
const threadContextRef = useRef4(threadContext);
|
|
1620
|
+
threadContextRef.current = threadContext;
|
|
1621
|
+
const currentThreadIdRef = useRef4(threadContext.currentThreadId);
|
|
1622
|
+
useEffect3(() => {
|
|
1623
|
+
currentThreadIdRef.current = threadContext.currentThreadId;
|
|
1624
|
+
}, [threadContext.currentThreadId]);
|
|
1625
|
+
const publicKeyRef = useRef4(publicKey);
|
|
1626
|
+
useEffect3(() => {
|
|
1627
|
+
publicKeyRef.current = publicKey;
|
|
1628
|
+
}, [publicKey]);
|
|
1629
|
+
const getPublicKey = useCallback3(() => publicKeyRef.current, []);
|
|
1630
|
+
const lastSubscribedThreadRef = useRef4(null);
|
|
1631
|
+
const { showNotification } = useNotification();
|
|
1632
|
+
const eventControllerRef = useRef4(null);
|
|
1633
|
+
const walletHandlerRef = useRef4(null);
|
|
1634
|
+
const {
|
|
1635
|
+
backendStateRef,
|
|
1636
|
+
polling,
|
|
1637
|
+
messageController,
|
|
1638
|
+
isRunning,
|
|
1639
|
+
setIsRunning,
|
|
1640
|
+
ensureInitialState,
|
|
1641
|
+
setSystemEventsHandler,
|
|
1642
|
+
backendApiRef
|
|
1643
|
+
} = useRuntimeOrchestrator(backendUrl, { getPublicKey });
|
|
1644
|
+
if (!walletHandlerRef.current) {
|
|
1645
|
+
walletHandlerRef.current = new WalletHandler({
|
|
1646
|
+
backendApiRef,
|
|
1647
|
+
onWalletTxRequest,
|
|
1648
|
+
publicKey,
|
|
1649
|
+
showNotification,
|
|
1650
|
+
applySessionMessagesToThread: (threadId, msgs) => {
|
|
1651
|
+
messageController.inbound(threadId, msgs);
|
|
1652
|
+
},
|
|
1653
|
+
getCurrentThreadId: () => currentThreadIdRef.current
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
if (!eventControllerRef.current) {
|
|
1657
|
+
eventControllerRef.current = new EventController({
|
|
1658
|
+
backendApiRef,
|
|
1659
|
+
backendStateRef,
|
|
1660
|
+
showNotification,
|
|
1661
|
+
handleWalletTxRequest: (sessionId, threadId, request) => {
|
|
1662
|
+
var _a;
|
|
1663
|
+
(_a = walletHandlerRef.current) == null ? void 0 : _a.handleRequest(sessionId, threadId, request);
|
|
1664
|
+
},
|
|
1665
|
+
setThreadMetadata: threadContext.setThreadMetadata
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
const handleSystemEvents = useCallback3(
|
|
1669
|
+
(sessionId, threadId, events) => {
|
|
1670
|
+
var _a;
|
|
1671
|
+
(_a = eventControllerRef.current) == null ? void 0 : _a.handleBackendSystemEvents(sessionId, threadId, events);
|
|
1672
|
+
},
|
|
1673
|
+
[]
|
|
1674
|
+
);
|
|
1675
|
+
useEffect3(() => {
|
|
1676
|
+
setSystemEventsHandler(handleSystemEvents);
|
|
1677
|
+
return () => {
|
|
1678
|
+
setSystemEventsHandler(null);
|
|
1679
|
+
};
|
|
1680
|
+
}, [handleSystemEvents, setSystemEventsHandler]);
|
|
1681
|
+
const [updateSubscriptionsTick, setUpdateSubscriptionsTick] = useState3(0);
|
|
1682
|
+
const bumpUpdateSubscriptions = useCallback3(() => {
|
|
1683
|
+
setUpdateSubscriptionsTick((prev) => prev + 1);
|
|
1684
|
+
}, []);
|
|
1685
|
+
useEffect3(() => {
|
|
1686
|
+
void ensureInitialState(threadContext.currentThreadId);
|
|
1687
|
+
}, [ensureInitialState, threadContext.currentThreadId]);
|
|
1688
|
+
useEffect3(() => {
|
|
1689
|
+
const threadId = threadContext.currentThreadId;
|
|
1690
|
+
const isCurrentlyRunning = isThreadRunning(backendStateRef.current, threadId);
|
|
1691
|
+
setIsRunning(isCurrentlyRunning);
|
|
1692
|
+
}, [threadContext.currentThreadId, setIsRunning]);
|
|
1693
|
+
const currentMessages = threadContext.getThreadMessages(threadContext.currentThreadId);
|
|
1694
|
+
useEffect3(() => {
|
|
1695
|
+
if (!publicKey) return;
|
|
1696
|
+
const fetchThreadList = async () => {
|
|
1697
|
+
var _a, _b;
|
|
1698
|
+
try {
|
|
1699
|
+
const threadList = await backendApiRef.current.fetchThreads(publicKey);
|
|
1700
|
+
const currentContext = threadContextRef.current;
|
|
1701
|
+
const newMetadata = new Map(currentContext.threadMetadata);
|
|
1702
|
+
let maxChatNum = currentContext.threadCnt;
|
|
1703
|
+
for (const thread of threadList) {
|
|
1704
|
+
const rawTitle = (_a = thread.title) != null ? _a : "";
|
|
1705
|
+
const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
|
|
1706
|
+
const lastActive = thread.last_active_at || thread.updated_at || thread.created_at || ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
|
|
1707
|
+
newMetadata.set(thread.session_id, {
|
|
1708
|
+
title,
|
|
1709
|
+
status: thread.is_archived ? "archived" : "regular",
|
|
1710
|
+
lastActiveAt: lastActive
|
|
1711
|
+
});
|
|
1712
|
+
const match = title.match(/^Chat (\d+)$/);
|
|
1713
|
+
if (match) {
|
|
1714
|
+
const num = parseInt(match[1], 10);
|
|
1715
|
+
if (num > maxChatNum) {
|
|
1716
|
+
maxChatNum = num;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
currentContext.setThreadMetadata(newMetadata);
|
|
1721
|
+
if (maxChatNum > currentContext.threadCnt) {
|
|
1722
|
+
currentContext.setThreadCnt(maxChatNum);
|
|
1723
|
+
}
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
console.error("Failed to fetch thread list:", error);
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
void fetchThreadList();
|
|
1729
|
+
}, [publicKey]);
|
|
1730
|
+
const threadListAdapter = useMemo2(() => {
|
|
1731
|
+
const backendState = backendStateRef.current;
|
|
1732
|
+
const { regularThreads, archivedThreads } = buildThreadLists(threadContext.threadMetadata);
|
|
1733
|
+
const preparePendingThread = (threadId) => {
|
|
1734
|
+
const previousPendingId = backendState.creatingThreadId;
|
|
1735
|
+
if (previousPendingId && previousPendingId !== threadId) {
|
|
1736
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1737
|
+
const next = new Map(prev);
|
|
1738
|
+
next.delete(previousPendingId);
|
|
1739
|
+
return next;
|
|
1740
|
+
});
|
|
1741
|
+
threadContext.setThreads((prev) => {
|
|
1742
|
+
const next = new Map(prev);
|
|
1743
|
+
next.delete(previousPendingId);
|
|
1744
|
+
return next;
|
|
1745
|
+
});
|
|
1746
|
+
backendState.pendingChat.delete(previousPendingId);
|
|
1747
|
+
backendState.pendingSystem.delete(previousPendingId);
|
|
1748
|
+
backendState.tempToBackendId.delete(previousPendingId);
|
|
1749
|
+
backendState.skipInitialFetch.delete(previousPendingId);
|
|
1750
|
+
}
|
|
1751
|
+
backendState.creatingThreadId = threadId;
|
|
1752
|
+
backendState.pendingChat.delete(threadId);
|
|
1753
|
+
backendState.pendingSystem.delete(threadId);
|
|
1754
|
+
threadContext.setThreadMetadata(
|
|
1755
|
+
(prev) => new Map(prev).set(threadId, {
|
|
1756
|
+
title: "New Chat",
|
|
1757
|
+
status: "pending",
|
|
1758
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1759
|
+
})
|
|
1760
|
+
);
|
|
1761
|
+
threadContext.setThreadMessages(threadId, []);
|
|
1762
|
+
threadContext.setCurrentThreadId(threadId);
|
|
1763
|
+
setIsRunning(false);
|
|
1764
|
+
threadContext.bumpThreadViewKey();
|
|
1765
|
+
};
|
|
1766
|
+
const findPendingThreadId = () => {
|
|
1767
|
+
if (backendState.creatingThreadId) return backendState.creatingThreadId;
|
|
1768
|
+
for (const [id, meta] of threadContext.threadMetadata.entries()) {
|
|
1769
|
+
if (meta.status === "pending") return id;
|
|
1770
|
+
}
|
|
1771
|
+
return null;
|
|
1772
|
+
};
|
|
1773
|
+
return {
|
|
1774
|
+
threadId: threadContext.currentThreadId,
|
|
1775
|
+
threads: regularThreads,
|
|
1776
|
+
archivedThreads,
|
|
1777
|
+
onSwitchToNewThread: async () => {
|
|
1778
|
+
var _a;
|
|
1779
|
+
const previousThreadId = currentThreadIdRef.current;
|
|
1780
|
+
polling.stopAll();
|
|
1781
|
+
if (isRunning && isThreadReady(backendState, previousThreadId)) {
|
|
1782
|
+
const backendId = resolveThreadId(backendState, previousThreadId);
|
|
1783
|
+
void backendApiRef.current.postInterrupt(backendId);
|
|
1784
|
+
}
|
|
1785
|
+
const pendingId = findPendingThreadId();
|
|
1786
|
+
if (pendingId) {
|
|
1787
|
+
preparePendingThread(pendingId);
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
if (backendState.createThreadPromise) {
|
|
1791
|
+
preparePendingThread((_a = backendState.creatingThreadId) != null ? _a : `temp-${crypto.randomUUID()}`);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
const tempId = `temp-${crypto.randomUUID()}`;
|
|
1795
|
+
preparePendingThread(tempId);
|
|
1796
|
+
const createPromise = backendApiRef.current.createThread(publicKey, void 0).then(async (newThread) => {
|
|
1797
|
+
var _a2;
|
|
1798
|
+
const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : tempId;
|
|
1799
|
+
const backendId = newThread.session_id;
|
|
1800
|
+
setBackendMapping(backendState, uiThreadId, backendId);
|
|
1801
|
+
markSkipInitialFetch(backendState, uiThreadId);
|
|
1802
|
+
bumpUpdateSubscriptions();
|
|
1803
|
+
const backendTitle = newThread.title;
|
|
1804
|
+
if (backendTitle && !isPlaceholderTitle(backendTitle)) {
|
|
1805
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1806
|
+
var _a3;
|
|
1807
|
+
const next = new Map(prev);
|
|
1808
|
+
const existing = next.get(uiThreadId);
|
|
1809
|
+
const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
|
|
1810
|
+
next.set(uiThreadId, {
|
|
1811
|
+
title: backendTitle,
|
|
1812
|
+
status: nextStatus,
|
|
1813
|
+
lastActiveAt: (_a3 = existing == null ? void 0 : existing.lastActiveAt) != null ? _a3 : (/* @__PURE__ */ new Date()).toISOString()
|
|
1814
|
+
});
|
|
1815
|
+
return next;
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
if (backendState.creatingThreadId === uiThreadId) {
|
|
1819
|
+
backendState.creatingThreadId = null;
|
|
1820
|
+
}
|
|
1821
|
+
const pendingMessages = backendState.pendingChat.get(uiThreadId);
|
|
1822
|
+
if (pendingMessages == null ? void 0 : pendingMessages.length) {
|
|
1823
|
+
backendState.pendingChat.delete(uiThreadId);
|
|
1824
|
+
for (const text of pendingMessages) {
|
|
1825
|
+
try {
|
|
1826
|
+
const activePublicKey = publicKeyRef.current;
|
|
1827
|
+
if (activePublicKey) {
|
|
1828
|
+
await backendApiRef.current.postChatMessage(
|
|
1829
|
+
backendId,
|
|
1830
|
+
text,
|
|
1831
|
+
activePublicKey
|
|
1832
|
+
);
|
|
1833
|
+
} else {
|
|
1834
|
+
await backendApiRef.current.postChatMessage(backendId, text);
|
|
1835
|
+
}
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
console.error("Failed to send queued message:", error);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (currentThreadIdRef.current === uiThreadId) {
|
|
1841
|
+
polling == null ? void 0 : polling.start(uiThreadId);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}).catch((error) => {
|
|
1845
|
+
var _a2;
|
|
1846
|
+
console.error("Failed to create new thread:", error);
|
|
1847
|
+
const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : tempId;
|
|
1848
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1849
|
+
const next = new Map(prev);
|
|
1850
|
+
next.delete(failedId);
|
|
1851
|
+
return next;
|
|
1852
|
+
});
|
|
1853
|
+
threadContext.setThreads((prev) => {
|
|
1854
|
+
const next = new Map(prev);
|
|
1855
|
+
next.delete(failedId);
|
|
1856
|
+
return next;
|
|
1857
|
+
});
|
|
1858
|
+
if (backendState.creatingThreadId === failedId) {
|
|
1859
|
+
backendState.creatingThreadId = null;
|
|
1860
|
+
}
|
|
1861
|
+
}).finally(() => {
|
|
1862
|
+
backendState.createThreadPromise = null;
|
|
1863
|
+
});
|
|
1864
|
+
backendState.createThreadPromise = createPromise;
|
|
1865
|
+
},
|
|
1866
|
+
onSwitchToThread: (threadId) => {
|
|
1867
|
+
const previousThreadId = currentThreadIdRef.current;
|
|
1868
|
+
polling.stopAll();
|
|
1869
|
+
if (isRunning && isThreadReady(backendState, previousThreadId)) {
|
|
1870
|
+
const backendId = resolveThreadId(backendState, previousThreadId);
|
|
1871
|
+
void backendApiRef.current.postInterrupt(backendId);
|
|
1872
|
+
}
|
|
1873
|
+
threadContext.setCurrentThreadId(threadId);
|
|
1874
|
+
},
|
|
1875
|
+
onRename: async (threadId, newTitle) => {
|
|
1876
|
+
var _a, _b;
|
|
1877
|
+
const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
|
|
1878
|
+
const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
|
|
1879
|
+
threadContext.updateThreadMetadata(threadId, {
|
|
1880
|
+
title: normalizedTitle
|
|
1881
|
+
});
|
|
1882
|
+
try {
|
|
1883
|
+
await backendApiRef.current.renameThread(threadId, newTitle);
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
console.error("Failed to rename thread:", error);
|
|
1886
|
+
threadContext.updateThreadMetadata(threadId, { title: previousTitle });
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
onArchive: async (threadId) => {
|
|
1890
|
+
threadContext.updateThreadMetadata(threadId, { status: "archived" });
|
|
1891
|
+
try {
|
|
1892
|
+
await backendApiRef.current.archiveThread(threadId);
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
console.error("Failed to archive thread:", error);
|
|
1895
|
+
threadContext.updateThreadMetadata(threadId, { status: "regular" });
|
|
1896
|
+
}
|
|
1897
|
+
},
|
|
1898
|
+
onUnarchive: async (threadId) => {
|
|
1899
|
+
threadContext.updateThreadMetadata(threadId, { status: "regular" });
|
|
1900
|
+
try {
|
|
1901
|
+
await backendApiRef.current.unarchiveThread(threadId);
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
console.error("Failed to unarchive thread:", error);
|
|
1904
|
+
threadContext.updateThreadMetadata(threadId, { status: "archived" });
|
|
1905
|
+
}
|
|
1906
|
+
},
|
|
1907
|
+
onDelete: async (threadId) => {
|
|
1908
|
+
try {
|
|
1909
|
+
await backendApiRef.current.deleteThread(threadId);
|
|
1910
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1911
|
+
const next = new Map(prev);
|
|
1912
|
+
next.delete(threadId);
|
|
1913
|
+
return next;
|
|
1914
|
+
});
|
|
1915
|
+
threadContext.setThreads((prev) => {
|
|
1916
|
+
const next = new Map(prev);
|
|
1917
|
+
next.delete(threadId);
|
|
1918
|
+
return next;
|
|
1919
|
+
});
|
|
1920
|
+
backendState.pendingChat.delete(threadId);
|
|
1921
|
+
backendState.pendingSystem.delete(threadId);
|
|
1922
|
+
backendState.tempToBackendId.delete(threadId);
|
|
1923
|
+
backendState.skipInitialFetch.delete(threadId);
|
|
1924
|
+
backendState.runningThreads.delete(threadId);
|
|
1925
|
+
if (backendState.creatingThreadId === threadId) {
|
|
1926
|
+
backendState.creatingThreadId = null;
|
|
1927
|
+
}
|
|
1928
|
+
if (threadContext.currentThreadId === threadId) {
|
|
1929
|
+
const firstRegularThread = Array.from(threadContext.threadMetadata.entries()).find(
|
|
1930
|
+
([id, meta]) => meta.status === "regular" && id !== threadId
|
|
1931
|
+
);
|
|
1932
|
+
if (firstRegularThread) {
|
|
1933
|
+
threadContext.setCurrentThreadId(firstRegularThread[0]);
|
|
1934
|
+
} else {
|
|
1935
|
+
const defaultId = "default-session";
|
|
1936
|
+
threadContext.setThreadMetadata(
|
|
1937
|
+
(prev) => new Map(prev).set(defaultId, {
|
|
1938
|
+
title: "New Chat",
|
|
1939
|
+
status: "regular",
|
|
1940
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1941
|
+
})
|
|
1942
|
+
);
|
|
1943
|
+
threadContext.setThreadMessages(defaultId, []);
|
|
1944
|
+
threadContext.setCurrentThreadId(defaultId);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
console.error("Failed to delete thread:", error);
|
|
1949
|
+
throw error;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
}, [
|
|
1954
|
+
backendApiRef,
|
|
1955
|
+
polling,
|
|
1956
|
+
publicKey,
|
|
1957
|
+
backendStateRef,
|
|
1958
|
+
setIsRunning,
|
|
1959
|
+
threadContext,
|
|
1960
|
+
threadContext.currentThreadId,
|
|
1961
|
+
threadContext.threadMetadata,
|
|
1962
|
+
bumpUpdateSubscriptions
|
|
1963
|
+
]);
|
|
1964
|
+
useEffect3(() => {
|
|
1965
|
+
const threadId = threadContext.currentThreadId;
|
|
1966
|
+
if (!isTempThreadId(threadId)) return;
|
|
1967
|
+
if (!isThreadReady(backendStateRef.current, threadId)) return;
|
|
1968
|
+
void messageController.flushPendingChat(threadId);
|
|
1969
|
+
}, [messageController, backendStateRef, threadContext.currentThreadId]);
|
|
1970
|
+
const runtime = useExternalStoreRuntime({
|
|
1971
|
+
messages: currentMessages,
|
|
1972
|
+
setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
|
|
1973
|
+
isRunning,
|
|
1974
|
+
onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
|
|
1975
|
+
onCancel: () => messageController.cancel(threadContext.currentThreadId),
|
|
1976
|
+
convertMessage: (msg) => msg,
|
|
1977
|
+
adapters: { threadList: threadListAdapter }
|
|
1978
|
+
});
|
|
1979
|
+
useEffect3(() => {
|
|
1980
|
+
const threadId = threadContext.currentThreadId;
|
|
1981
|
+
if (isTempThreadId(threadId)) return;
|
|
1982
|
+
const hasUserMessages = currentMessages.some((msg) => msg.role === "user");
|
|
1983
|
+
if (hasUserMessages) {
|
|
1984
|
+
void messageController.flushPendingSystem(threadId);
|
|
1985
|
+
}
|
|
1986
|
+
}, [currentMessages, messageController, threadContext.currentThreadId]);
|
|
1987
|
+
useEffect3(() => {
|
|
1988
|
+
return () => {
|
|
1989
|
+
var _a;
|
|
1990
|
+
polling.stopAll();
|
|
1991
|
+
(_a = eventControllerRef.current) == null ? void 0 : _a.cleanup();
|
|
1992
|
+
};
|
|
1993
|
+
}, [polling]);
|
|
1994
|
+
useEffect3(() => {
|
|
1995
|
+
var _a, _b, _c;
|
|
1996
|
+
const backendState = backendStateRef.current;
|
|
1997
|
+
const threadId = threadContext.currentThreadId;
|
|
1998
|
+
const isReady = isThreadReady(backendState, threadId);
|
|
1999
|
+
if (!isReady) {
|
|
2000
|
+
lastSubscribedThreadRef.current = null;
|
|
2001
|
+
(_a = eventControllerRef.current) == null ? void 0 : _a.setSubscribableSessionId(null);
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const isCurrentlyRunning = isThreadRunning(backendState, threadId);
|
|
2005
|
+
if (isCurrentlyRunning) {
|
|
2006
|
+
const sessionId = resolveThreadId(backendState, threadId);
|
|
2007
|
+
(_b = eventControllerRef.current) == null ? void 0 : _b.setSubscribableSessionId(sessionId);
|
|
2008
|
+
lastSubscribedThreadRef.current = threadId;
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (lastSubscribedThreadRef.current !== threadId) {
|
|
2012
|
+
(_c = eventControllerRef.current) == null ? void 0 : _c.setSubscribableSessionId(null);
|
|
2013
|
+
}
|
|
2014
|
+
}, [backendStateRef, threadContext.currentThreadId, updateSubscriptionsTick, isRunning]);
|
|
2015
|
+
return /* @__PURE__ */ jsx3(
|
|
2016
|
+
RuntimeActionsProvider,
|
|
2017
|
+
{
|
|
2018
|
+
value: {
|
|
2019
|
+
sendSystemMessage: (message) => messageController.outboundSystem(threadContext.currentThreadId, message)
|
|
2020
|
+
},
|
|
2021
|
+
children: /* @__PURE__ */ jsx3(AssistantRuntimeProvider, { runtime, children })
|
|
2022
|
+
}
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
function AomiRuntimeProviderWithNotifications(props) {
|
|
2026
|
+
return /* @__PURE__ */ jsx3(NotificationProvider, { children: /* @__PURE__ */ jsx3(AomiRuntimeProvider, __spreadValues({}, props)) });
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// src/lib/utils.ts
|
|
2030
|
+
import { clsx } from "clsx";
|
|
2031
|
+
import { twMerge } from "tailwind-merge";
|
|
2032
|
+
function cn(...inputs) {
|
|
2033
|
+
return twMerge(clsx(inputs));
|
|
2034
|
+
}
|
|
2035
|
+
export {
|
|
2036
|
+
AomiRuntimeProvider,
|
|
2037
|
+
AomiRuntimeProviderWithNotifications,
|
|
2038
|
+
BackendApi,
|
|
2039
|
+
NotificationProvider,
|
|
2040
|
+
RuntimeActionsProvider,
|
|
2041
|
+
ThreadContextProvider,
|
|
2042
|
+
WalletSystemMessageEmitter,
|
|
2043
|
+
cn,
|
|
2044
|
+
toInboundSystem as constructSystemMessage,
|
|
2045
|
+
toInboundMessage as constructThreadMessage,
|
|
2046
|
+
formatAddress,
|
|
2047
|
+
getNetworkName,
|
|
2048
|
+
normalizeWalletError,
|
|
2049
|
+
pickInjectedProvider,
|
|
2050
|
+
toHexQuantity,
|
|
2051
|
+
useCurrentThreadMessages,
|
|
2052
|
+
useCurrentThreadMetadata,
|
|
2053
|
+
useNotification,
|
|
2054
|
+
useRuntimeActions,
|
|
2055
|
+
useThreadContext
|
|
2056
|
+
};
|
|
2057
|
+
//# sourceMappingURL=index.js.map
|