@aomi-labs/react 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2562 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +471 -0
- package/dist/index.d.ts +471 -0
- package/dist/index.js +2552 -0
- package/dist/index.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,2552 @@
|
|
|
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
|
+
// packages/react/src/backend/sse.ts
|
|
22
|
+
function extractSseData(rawEvent) {
|
|
23
|
+
const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
|
|
24
|
+
if (!dataLines.length) return null;
|
|
25
|
+
return dataLines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
async function readSseStream(stream, signal, onMessage) {
|
|
28
|
+
const reader = stream.getReader();
|
|
29
|
+
const decoder = new TextDecoder();
|
|
30
|
+
let buffer = "";
|
|
31
|
+
try {
|
|
32
|
+
while (!signal.aborted) {
|
|
33
|
+
const { value, done } = await reader.read();
|
|
34
|
+
if (done) break;
|
|
35
|
+
buffer += decoder.decode(value, { stream: true });
|
|
36
|
+
buffer = buffer.replace(/\r/g, "");
|
|
37
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
38
|
+
while (separatorIndex >= 0) {
|
|
39
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
40
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
41
|
+
const data = extractSseData(rawEvent);
|
|
42
|
+
if (data) {
|
|
43
|
+
onMessage(data);
|
|
44
|
+
}
|
|
45
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} finally {
|
|
49
|
+
reader.releaseLock();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function createSseSubscriber({
|
|
53
|
+
backendUrl,
|
|
54
|
+
getHeaders,
|
|
55
|
+
shouldLog = process.env.NODE_ENV !== "production"
|
|
56
|
+
}) {
|
|
57
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
58
|
+
const subscribe2 = (sessionId, onUpdate, onError) => {
|
|
59
|
+
const existing = subscriptions.get(sessionId);
|
|
60
|
+
const listener = { onUpdate, onError };
|
|
61
|
+
if (existing) {
|
|
62
|
+
existing.listeners.add(listener);
|
|
63
|
+
if (shouldLog) {
|
|
64
|
+
console.debug("[aomi][sse] listener added", {
|
|
65
|
+
sessionId,
|
|
66
|
+
listeners: existing.listeners.size
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return () => {
|
|
70
|
+
existing.listeners.delete(listener);
|
|
71
|
+
if (shouldLog) {
|
|
72
|
+
console.debug("[aomi][sse] listener removed", {
|
|
73
|
+
sessionId,
|
|
74
|
+
listeners: existing.listeners.size
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (existing.listeners.size === 0) {
|
|
78
|
+
existing.stop("unsubscribe");
|
|
79
|
+
if (subscriptions.get(sessionId) === existing) {
|
|
80
|
+
subscriptions.delete(sessionId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const subscription = {
|
|
86
|
+
abortController: null,
|
|
87
|
+
retries: 0,
|
|
88
|
+
retryTimer: null,
|
|
89
|
+
stopped: false,
|
|
90
|
+
listeners: /* @__PURE__ */ new Set([listener]),
|
|
91
|
+
stop: (reason) => {
|
|
92
|
+
var _a;
|
|
93
|
+
subscription.stopped = true;
|
|
94
|
+
if (subscription.retryTimer) {
|
|
95
|
+
clearTimeout(subscription.retryTimer);
|
|
96
|
+
subscription.retryTimer = null;
|
|
97
|
+
}
|
|
98
|
+
(_a = subscription.abortController) == null ? void 0 : _a.abort();
|
|
99
|
+
subscription.abortController = null;
|
|
100
|
+
if (shouldLog) {
|
|
101
|
+
console.debug("[aomi][sse] stop", {
|
|
102
|
+
sessionId,
|
|
103
|
+
reason,
|
|
104
|
+
retries: subscription.retries
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const scheduleRetry = () => {
|
|
110
|
+
if (subscription.stopped) return;
|
|
111
|
+
subscription.retries += 1;
|
|
112
|
+
const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
|
|
113
|
+
if (shouldLog) {
|
|
114
|
+
console.debug("[aomi][sse] retry scheduled", {
|
|
115
|
+
sessionId,
|
|
116
|
+
delayMs,
|
|
117
|
+
retries: subscription.retries
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
subscription.retryTimer = setTimeout(() => {
|
|
121
|
+
void open();
|
|
122
|
+
}, delayMs);
|
|
123
|
+
};
|
|
124
|
+
const open = async () => {
|
|
125
|
+
var _a;
|
|
126
|
+
if (subscription.stopped) return;
|
|
127
|
+
if (subscription.retryTimer) {
|
|
128
|
+
clearTimeout(subscription.retryTimer);
|
|
129
|
+
subscription.retryTimer = null;
|
|
130
|
+
}
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
subscription.abortController = controller;
|
|
133
|
+
const openedAt = Date.now();
|
|
134
|
+
try {
|
|
135
|
+
const response = await fetch(`${backendUrl}/api/updates`, {
|
|
136
|
+
headers: getHeaders(sessionId),
|
|
137
|
+
signal: controller.signal
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`SSE HTTP ${response.status}: ${response.statusText}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (!response.body) {
|
|
145
|
+
throw new Error("SSE response missing body");
|
|
146
|
+
}
|
|
147
|
+
subscription.retries = 0;
|
|
148
|
+
await readSseStream(response.body, controller.signal, (data) => {
|
|
149
|
+
var _a2, _b;
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = JSON.parse(data);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
for (const item of subscription.listeners) {
|
|
155
|
+
(_a2 = item.onError) == null ? void 0 : _a2.call(item, error);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const item of subscription.listeners) {
|
|
160
|
+
try {
|
|
161
|
+
item.onUpdate(parsed);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
(_b = item.onError) == null ? void 0 : _b.call(item, error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
if (shouldLog) {
|
|
168
|
+
console.debug("[aomi][sse] stream ended", {
|
|
169
|
+
sessionId,
|
|
170
|
+
aborted: controller.signal.aborted,
|
|
171
|
+
stopped: subscription.stopped,
|
|
172
|
+
durationMs: Date.now() - openedAt
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (!controller.signal.aborted && !subscription.stopped) {
|
|
177
|
+
for (const item of subscription.listeners) {
|
|
178
|
+
(_a = item.onError) == null ? void 0 : _a.call(item, error);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!subscription.stopped) {
|
|
183
|
+
scheduleRetry();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
subscriptions.set(sessionId, subscription);
|
|
187
|
+
void open();
|
|
188
|
+
return () => {
|
|
189
|
+
subscription.listeners.delete(listener);
|
|
190
|
+
if (shouldLog) {
|
|
191
|
+
console.debug("[aomi][sse] listener removed", {
|
|
192
|
+
sessionId,
|
|
193
|
+
listeners: subscription.listeners.size
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (subscription.listeners.size === 0) {
|
|
197
|
+
subscription.stop("unsubscribe");
|
|
198
|
+
if (subscriptions.get(sessionId) === subscription) {
|
|
199
|
+
subscriptions.delete(sessionId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
return { subscribe: subscribe2 };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// packages/react/src/backend/client.ts
|
|
208
|
+
var SESSION_ID_HEADER = "X-Session-Id";
|
|
209
|
+
var API_KEY_HEADER = "X-API-Key";
|
|
210
|
+
function toQueryString(payload) {
|
|
211
|
+
const params = new URLSearchParams();
|
|
212
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
213
|
+
if (value === void 0 || value === null) continue;
|
|
214
|
+
params.set(key, String(value));
|
|
215
|
+
}
|
|
216
|
+
const qs = params.toString();
|
|
217
|
+
return qs ? `?${qs}` : "";
|
|
218
|
+
}
|
|
219
|
+
function withSessionHeader(sessionId, init) {
|
|
220
|
+
const headers = new Headers(init);
|
|
221
|
+
headers.set(SESSION_ID_HEADER, sessionId);
|
|
222
|
+
return headers;
|
|
223
|
+
}
|
|
224
|
+
async function postState(backendUrl, path, payload, sessionId, apiKey) {
|
|
225
|
+
const query = toQueryString(payload);
|
|
226
|
+
const url = `${backendUrl}${path}${query}`;
|
|
227
|
+
const headers = new Headers(withSessionHeader(sessionId));
|
|
228
|
+
if (apiKey) {
|
|
229
|
+
headers.set(API_KEY_HEADER, apiKey);
|
|
230
|
+
}
|
|
231
|
+
const response = await fetch(url, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
237
|
+
}
|
|
238
|
+
return await response.json();
|
|
239
|
+
}
|
|
240
|
+
var BackendApi = class {
|
|
241
|
+
constructor(backendUrl) {
|
|
242
|
+
this.backendUrl = backendUrl;
|
|
243
|
+
this.sseSubscriber = createSseSubscriber({
|
|
244
|
+
backendUrl,
|
|
245
|
+
getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" })
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async fetchState(sessionId, userState) {
|
|
249
|
+
const url = new URL("/api/state", this.backendUrl);
|
|
250
|
+
if (userState) {
|
|
251
|
+
url.searchParams.set("user_state", JSON.stringify(userState));
|
|
252
|
+
}
|
|
253
|
+
const response = await fetch(url.toString(), {
|
|
254
|
+
headers: withSessionHeader(sessionId)
|
|
255
|
+
});
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
258
|
+
}
|
|
259
|
+
return await response.json();
|
|
260
|
+
}
|
|
261
|
+
async postChatMessage(sessionId, message, namespace, publicKey, apiKey) {
|
|
262
|
+
const payload = { message, namespace };
|
|
263
|
+
if (publicKey) {
|
|
264
|
+
payload.public_key = publicKey;
|
|
265
|
+
}
|
|
266
|
+
return postState(
|
|
267
|
+
this.backendUrl,
|
|
268
|
+
"/api/chat",
|
|
269
|
+
payload,
|
|
270
|
+
sessionId,
|
|
271
|
+
apiKey
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
async postSystemMessage(sessionId, message) {
|
|
275
|
+
return postState(
|
|
276
|
+
this.backendUrl,
|
|
277
|
+
"/api/system",
|
|
278
|
+
{
|
|
279
|
+
message
|
|
280
|
+
},
|
|
281
|
+
sessionId
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
async postInterrupt(sessionId) {
|
|
285
|
+
return postState(
|
|
286
|
+
this.backendUrl,
|
|
287
|
+
"/api/interrupt",
|
|
288
|
+
{},
|
|
289
|
+
sessionId
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Subscribe to SSE updates for a session.
|
|
294
|
+
* Uses fetch streaming and reconnects on disconnects.
|
|
295
|
+
* Returns an unsubscribe function.
|
|
296
|
+
*/
|
|
297
|
+
subscribeSSE(sessionId, onUpdate, onError) {
|
|
298
|
+
return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
|
|
299
|
+
}
|
|
300
|
+
async fetchThreads(publicKey) {
|
|
301
|
+
const url = `${this.backendUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
|
|
302
|
+
const response = await fetch(url);
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
|
|
305
|
+
}
|
|
306
|
+
return await response.json();
|
|
307
|
+
}
|
|
308
|
+
async fetchThread(sessionId) {
|
|
309
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
310
|
+
const response = await fetch(url, {
|
|
311
|
+
headers: withSessionHeader(sessionId)
|
|
312
|
+
});
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
315
|
+
}
|
|
316
|
+
return await response.json();
|
|
317
|
+
}
|
|
318
|
+
async createThread(threadId, publicKey) {
|
|
319
|
+
const body = {};
|
|
320
|
+
if (publicKey) body.public_key = publicKey;
|
|
321
|
+
const url = `${this.backendUrl}/api/sessions`;
|
|
322
|
+
const response = await fetch(url, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers: withSessionHeader(threadId, {
|
|
325
|
+
"Content-Type": "application/json"
|
|
326
|
+
}),
|
|
327
|
+
body: JSON.stringify(body)
|
|
328
|
+
});
|
|
329
|
+
if (!response.ok) {
|
|
330
|
+
throw new Error(`Failed to create thread: HTTP ${response.status}`);
|
|
331
|
+
}
|
|
332
|
+
return await response.json();
|
|
333
|
+
}
|
|
334
|
+
async archiveThread(sessionId) {
|
|
335
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
|
|
336
|
+
const response = await fetch(url, {
|
|
337
|
+
method: "POST",
|
|
338
|
+
headers: withSessionHeader(sessionId)
|
|
339
|
+
});
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
throw new Error(`Failed to archive thread: HTTP ${response.status}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async unarchiveThread(sessionId) {
|
|
345
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
|
|
346
|
+
const response = await fetch(url, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: withSessionHeader(sessionId)
|
|
349
|
+
});
|
|
350
|
+
if (!response.ok) {
|
|
351
|
+
throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async deleteThread(sessionId) {
|
|
355
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
356
|
+
const response = await fetch(url, {
|
|
357
|
+
method: "DELETE",
|
|
358
|
+
headers: withSessionHeader(sessionId)
|
|
359
|
+
});
|
|
360
|
+
if (!response.ok) {
|
|
361
|
+
throw new Error(`Failed to delete thread: HTTP ${response.status}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async renameThread(sessionId, newTitle) {
|
|
365
|
+
const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
366
|
+
const response = await fetch(url, {
|
|
367
|
+
method: "PATCH",
|
|
368
|
+
headers: withSessionHeader(sessionId, {
|
|
369
|
+
"Content-Type": "application/json"
|
|
370
|
+
}),
|
|
371
|
+
body: JSON.stringify({ title: newTitle })
|
|
372
|
+
});
|
|
373
|
+
if (!response.ok) {
|
|
374
|
+
throw new Error(`Failed to rename thread: HTTP ${response.status}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async getSystemEvents(sessionId, count) {
|
|
378
|
+
const url = new URL("/api/events", this.backendUrl);
|
|
379
|
+
if (count !== void 0) {
|
|
380
|
+
url.searchParams.set("count", String(count));
|
|
381
|
+
}
|
|
382
|
+
const response = await fetch(url.toString(), {
|
|
383
|
+
headers: withSessionHeader(sessionId)
|
|
384
|
+
});
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
if (response.status === 404) return [];
|
|
387
|
+
throw new Error(`Failed to get system events: HTTP ${response.status}`);
|
|
388
|
+
}
|
|
389
|
+
return await response.json();
|
|
390
|
+
}
|
|
391
|
+
// ===========================================================================
|
|
392
|
+
// Control API
|
|
393
|
+
// ===========================================================================
|
|
394
|
+
/**
|
|
395
|
+
* Get allowed namespaces for the current request context.
|
|
396
|
+
*/
|
|
397
|
+
async getNamespaces(sessionId, publicKey, apiKey) {
|
|
398
|
+
const url = new URL("/api/control/namespaces", this.backendUrl);
|
|
399
|
+
if (publicKey) {
|
|
400
|
+
url.searchParams.set("public_key", publicKey);
|
|
401
|
+
}
|
|
402
|
+
console.log("[BackendApi.getNamespaces]", {
|
|
403
|
+
backendUrl: this.backendUrl,
|
|
404
|
+
fullUrl: url.toString(),
|
|
405
|
+
sessionId,
|
|
406
|
+
publicKey
|
|
407
|
+
});
|
|
408
|
+
const headers = new Headers(withSessionHeader(sessionId));
|
|
409
|
+
if (apiKey) {
|
|
410
|
+
headers.set(API_KEY_HEADER, apiKey);
|
|
411
|
+
}
|
|
412
|
+
const response = await fetch(url.toString(), { headers });
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
throw new Error(`Failed to get namespaces: HTTP ${response.status}`);
|
|
415
|
+
}
|
|
416
|
+
return await response.json();
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get available models.
|
|
420
|
+
*/
|
|
421
|
+
async getModels(sessionId) {
|
|
422
|
+
const url = new URL("/api/control/models", this.backendUrl);
|
|
423
|
+
console.log("[BackendApi.getModels]", {
|
|
424
|
+
backendUrl: this.backendUrl,
|
|
425
|
+
fullUrl: url.toString(),
|
|
426
|
+
sessionId
|
|
427
|
+
});
|
|
428
|
+
const response = await fetch(url.toString(), {
|
|
429
|
+
headers: withSessionHeader(sessionId)
|
|
430
|
+
});
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
throw new Error(`Failed to get models: HTTP ${response.status}`);
|
|
433
|
+
}
|
|
434
|
+
return await response.json();
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Set the model selection for a session.
|
|
438
|
+
*/
|
|
439
|
+
async setModel(sessionId, rig, namespace, apiKey) {
|
|
440
|
+
const payload = { rig };
|
|
441
|
+
if (namespace) {
|
|
442
|
+
payload.namespace = namespace;
|
|
443
|
+
}
|
|
444
|
+
return postState(this.backendUrl, "/api/control/model", payload, sessionId, apiKey);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// packages/react/src/runtime/aomi-runtime.tsx
|
|
449
|
+
import { useMemo as useMemo3 } from "react";
|
|
450
|
+
|
|
451
|
+
// packages/react/src/contexts/control-context.tsx
|
|
452
|
+
import {
|
|
453
|
+
createContext,
|
|
454
|
+
useCallback,
|
|
455
|
+
useContext,
|
|
456
|
+
useRef,
|
|
457
|
+
useState,
|
|
458
|
+
useEffect
|
|
459
|
+
} from "react";
|
|
460
|
+
|
|
461
|
+
// packages/react/src/utils/uuid.ts
|
|
462
|
+
function generateUUID() {
|
|
463
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
464
|
+
return crypto.randomUUID();
|
|
465
|
+
}
|
|
466
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
467
|
+
const r = Math.random() * 16 | 0;
|
|
468
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
469
|
+
return v.toString(16);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// packages/react/src/state/thread-store.ts
|
|
474
|
+
var shouldLogThreadUpdates = process.env.NODE_ENV !== "production";
|
|
475
|
+
var logThreadMetadataChange = (source, threadId, prev, next) => {
|
|
476
|
+
if (!shouldLogThreadUpdates) return;
|
|
477
|
+
if (!prev && !next) return;
|
|
478
|
+
if (!prev || !next) {
|
|
479
|
+
console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (prev.title !== next.title || prev.status !== next.status || prev.lastActiveAt !== next.lastActiveAt) {
|
|
483
|
+
console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
function initThreadControl() {
|
|
487
|
+
return {
|
|
488
|
+
model: null,
|
|
489
|
+
namespace: null,
|
|
490
|
+
controlDirty: false,
|
|
491
|
+
isProcessing: false
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
var ThreadStore = class {
|
|
495
|
+
constructor(options) {
|
|
496
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
497
|
+
this.subscribe = (listener) => {
|
|
498
|
+
this.listeners.add(listener);
|
|
499
|
+
return () => {
|
|
500
|
+
this.listeners.delete(listener);
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
this.getSnapshot = () => this.snapshot;
|
|
504
|
+
this.setCurrentThreadId = (threadId) => {
|
|
505
|
+
this.ensureThreadExists(threadId);
|
|
506
|
+
this.updateState({ currentThreadId: threadId });
|
|
507
|
+
};
|
|
508
|
+
this.bumpThreadViewKey = () => {
|
|
509
|
+
this.updateState({ threadViewKey: this.state.threadViewKey + 1 });
|
|
510
|
+
};
|
|
511
|
+
this.setThreadCnt = (updater) => {
|
|
512
|
+
const nextCnt = this.resolveStateAction(updater, this.state.threadCnt);
|
|
513
|
+
this.updateState({ threadCnt: nextCnt });
|
|
514
|
+
};
|
|
515
|
+
this.setThreads = (updater) => {
|
|
516
|
+
const nextThreads = this.resolveStateAction(updater, this.state.threads);
|
|
517
|
+
this.updateState({ threads: new Map(nextThreads) });
|
|
518
|
+
};
|
|
519
|
+
this.setThreadMetadata = (updater) => {
|
|
520
|
+
const prevMetadata = this.state.threadMetadata;
|
|
521
|
+
const nextMetadata = this.resolveStateAction(updater, prevMetadata);
|
|
522
|
+
for (const [threadId, next] of nextMetadata.entries()) {
|
|
523
|
+
logThreadMetadataChange(
|
|
524
|
+
"setThreadMetadata",
|
|
525
|
+
threadId,
|
|
526
|
+
prevMetadata.get(threadId),
|
|
527
|
+
next
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
for (const [threadId, prev] of prevMetadata.entries()) {
|
|
531
|
+
if (!nextMetadata.has(threadId)) {
|
|
532
|
+
logThreadMetadataChange("setThreadMetadata", threadId, prev, void 0);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
this.updateState({ threadMetadata: new Map(nextMetadata) });
|
|
536
|
+
};
|
|
537
|
+
this.setThreadMessages = (threadId, messages) => {
|
|
538
|
+
this.ensureThreadExists(threadId);
|
|
539
|
+
const nextThreads = new Map(this.state.threads);
|
|
540
|
+
nextThreads.set(threadId, messages);
|
|
541
|
+
this.updateState({ threads: nextThreads });
|
|
542
|
+
};
|
|
543
|
+
this.getThreadMessages = (threadId) => {
|
|
544
|
+
var _a;
|
|
545
|
+
return (_a = this.state.threads.get(threadId)) != null ? _a : [];
|
|
546
|
+
};
|
|
547
|
+
this.getThreadMetadata = (threadId) => {
|
|
548
|
+
return this.state.threadMetadata.get(threadId);
|
|
549
|
+
};
|
|
550
|
+
this.updateThreadMetadata = (threadId, updates) => {
|
|
551
|
+
const existing = this.state.threadMetadata.get(threadId);
|
|
552
|
+
if (!existing) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const next = __spreadValues(__spreadValues({}, existing), updates);
|
|
556
|
+
const nextMetadata = new Map(this.state.threadMetadata);
|
|
557
|
+
nextMetadata.set(threadId, next);
|
|
558
|
+
logThreadMetadataChange("updateThreadMetadata", threadId, existing, next);
|
|
559
|
+
this.updateState({ threadMetadata: nextMetadata });
|
|
560
|
+
};
|
|
561
|
+
var _a;
|
|
562
|
+
const initialThreadId = (_a = options == null ? void 0 : options.initialThreadId) != null ? _a : generateUUID();
|
|
563
|
+
this.state = {
|
|
564
|
+
currentThreadId: initialThreadId,
|
|
565
|
+
threadViewKey: 0,
|
|
566
|
+
threadCnt: 1,
|
|
567
|
+
threads: /* @__PURE__ */ new Map([[initialThreadId, []]]),
|
|
568
|
+
threadMetadata: /* @__PURE__ */ new Map([
|
|
569
|
+
[
|
|
570
|
+
initialThreadId,
|
|
571
|
+
{
|
|
572
|
+
title: "New Chat",
|
|
573
|
+
status: "pending",
|
|
574
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
575
|
+
control: initThreadControl()
|
|
576
|
+
}
|
|
577
|
+
]
|
|
578
|
+
])
|
|
579
|
+
};
|
|
580
|
+
this.snapshot = this.buildSnapshot();
|
|
581
|
+
}
|
|
582
|
+
emit() {
|
|
583
|
+
for (const listener of this.listeners) {
|
|
584
|
+
listener();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
resolveStateAction(updater, current) {
|
|
588
|
+
return typeof updater === "function" ? updater(current) : updater;
|
|
589
|
+
}
|
|
590
|
+
ensureThreadExists(threadId) {
|
|
591
|
+
if (!this.state.threadMetadata.has(threadId)) {
|
|
592
|
+
const nextMetadata = new Map(this.state.threadMetadata);
|
|
593
|
+
nextMetadata.set(threadId, {
|
|
594
|
+
title: "New Chat",
|
|
595
|
+
status: "regular",
|
|
596
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
597
|
+
control: initThreadControl()
|
|
598
|
+
});
|
|
599
|
+
this.state = __spreadProps(__spreadValues({}, this.state), { threadMetadata: nextMetadata });
|
|
600
|
+
}
|
|
601
|
+
if (!this.state.threads.has(threadId)) {
|
|
602
|
+
const nextThreads = new Map(this.state.threads);
|
|
603
|
+
nextThreads.set(threadId, []);
|
|
604
|
+
this.state = __spreadProps(__spreadValues({}, this.state), { threads: nextThreads });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
updateState(partial) {
|
|
608
|
+
this.state = __spreadValues(__spreadValues({}, this.state), partial);
|
|
609
|
+
this.snapshot = this.buildSnapshot();
|
|
610
|
+
this.emit();
|
|
611
|
+
}
|
|
612
|
+
buildSnapshot() {
|
|
613
|
+
return {
|
|
614
|
+
currentThreadId: this.state.currentThreadId,
|
|
615
|
+
setCurrentThreadId: this.setCurrentThreadId,
|
|
616
|
+
threadViewKey: this.state.threadViewKey,
|
|
617
|
+
bumpThreadViewKey: this.bumpThreadViewKey,
|
|
618
|
+
allThreads: this.state.threads,
|
|
619
|
+
setThreads: this.setThreads,
|
|
620
|
+
allThreadsMetadata: this.state.threadMetadata,
|
|
621
|
+
setThreadMetadata: this.setThreadMetadata,
|
|
622
|
+
threadCnt: this.state.threadCnt,
|
|
623
|
+
setThreadCnt: this.setThreadCnt,
|
|
624
|
+
getThreadMessages: this.getThreadMessages,
|
|
625
|
+
setThreadMessages: this.setThreadMessages,
|
|
626
|
+
getThreadMetadata: this.getThreadMetadata,
|
|
627
|
+
updateThreadMetadata: this.updateThreadMetadata
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// packages/react/src/contexts/control-context.tsx
|
|
633
|
+
import { jsx } from "react/jsx-runtime";
|
|
634
|
+
var API_KEY_STORAGE_KEY = "aomi_api_key";
|
|
635
|
+
var ControlContext = createContext(null);
|
|
636
|
+
function useControl() {
|
|
637
|
+
const ctx = useContext(ControlContext);
|
|
638
|
+
if (!ctx) {
|
|
639
|
+
throw new Error("useControl must be used within ControlContextProvider");
|
|
640
|
+
}
|
|
641
|
+
return ctx;
|
|
642
|
+
}
|
|
643
|
+
function ControlContextProvider({
|
|
644
|
+
children,
|
|
645
|
+
backendApi,
|
|
646
|
+
sessionId,
|
|
647
|
+
publicKey,
|
|
648
|
+
getThreadMetadata,
|
|
649
|
+
updateThreadMetadata
|
|
650
|
+
}) {
|
|
651
|
+
var _a, _b;
|
|
652
|
+
const [state, setStateInternal] = useState(() => ({
|
|
653
|
+
apiKey: null,
|
|
654
|
+
availableModels: [],
|
|
655
|
+
authorizedNamespaces: [],
|
|
656
|
+
defaultModel: null,
|
|
657
|
+
defaultNamespace: null
|
|
658
|
+
}));
|
|
659
|
+
const stateRef = useRef(state);
|
|
660
|
+
stateRef.current = state;
|
|
661
|
+
const backendApiRef = useRef(backendApi);
|
|
662
|
+
backendApiRef.current = backendApi;
|
|
663
|
+
const sessionIdRef = useRef(sessionId);
|
|
664
|
+
sessionIdRef.current = sessionId;
|
|
665
|
+
const publicKeyRef = useRef(publicKey);
|
|
666
|
+
publicKeyRef.current = publicKey;
|
|
667
|
+
const getThreadMetadataRef = useRef(getThreadMetadata);
|
|
668
|
+
getThreadMetadataRef.current = getThreadMetadata;
|
|
669
|
+
const updateThreadMetadataRef = useRef(updateThreadMetadata);
|
|
670
|
+
updateThreadMetadataRef.current = updateThreadMetadata;
|
|
671
|
+
const callbacks = useRef(/* @__PURE__ */ new Set());
|
|
672
|
+
const currentThreadMetadata = getThreadMetadata(sessionId);
|
|
673
|
+
const isProcessing = (_b = (_a = currentThreadMetadata == null ? void 0 : currentThreadMetadata.control) == null ? void 0 : _a.isProcessing) != null ? _b : false;
|
|
674
|
+
useEffect(() => {
|
|
675
|
+
var _a2, _b2;
|
|
676
|
+
try {
|
|
677
|
+
const storedApiKey = (_b2 = (_a2 = globalThis.localStorage) == null ? void 0 : _a2.getItem(API_KEY_STORAGE_KEY)) != null ? _b2 : null;
|
|
678
|
+
if (storedApiKey) {
|
|
679
|
+
setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { apiKey: storedApiKey }));
|
|
680
|
+
}
|
|
681
|
+
} catch (e) {
|
|
682
|
+
}
|
|
683
|
+
}, []);
|
|
684
|
+
useEffect(() => {
|
|
685
|
+
var _a2, _b2;
|
|
686
|
+
try {
|
|
687
|
+
if (state.apiKey) {
|
|
688
|
+
(_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(API_KEY_STORAGE_KEY, state.apiKey);
|
|
689
|
+
} else {
|
|
690
|
+
(_b2 = globalThis.localStorage) == null ? void 0 : _b2.removeItem(API_KEY_STORAGE_KEY);
|
|
691
|
+
}
|
|
692
|
+
} catch (e) {
|
|
693
|
+
}
|
|
694
|
+
}, [state.apiKey]);
|
|
695
|
+
useEffect(() => {
|
|
696
|
+
const fetchNamespaces = async () => {
|
|
697
|
+
var _a2, _b2;
|
|
698
|
+
try {
|
|
699
|
+
const namespaces = await backendApiRef.current.getNamespaces(
|
|
700
|
+
sessionIdRef.current,
|
|
701
|
+
publicKeyRef.current,
|
|
702
|
+
(_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
|
|
703
|
+
);
|
|
704
|
+
const defaultNs = namespaces.includes("default") ? "default" : (_b2 = namespaces[0]) != null ? _b2 : null;
|
|
705
|
+
setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
706
|
+
authorizedNamespaces: namespaces,
|
|
707
|
+
defaultNamespace: defaultNs
|
|
708
|
+
}));
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.error("Failed to fetch namespaces:", error);
|
|
711
|
+
setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
712
|
+
authorizedNamespaces: ["default"],
|
|
713
|
+
defaultNamespace: "default"
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
void fetchNamespaces();
|
|
718
|
+
}, [state.apiKey]);
|
|
719
|
+
useEffect(() => {
|
|
720
|
+
const fetchModels = async () => {
|
|
721
|
+
try {
|
|
722
|
+
const models = await backendApiRef.current.getModels(
|
|
723
|
+
sessionIdRef.current
|
|
724
|
+
);
|
|
725
|
+
setStateInternal((prev) => {
|
|
726
|
+
var _a2;
|
|
727
|
+
return __spreadProps(__spreadValues({}, prev), {
|
|
728
|
+
availableModels: models,
|
|
729
|
+
defaultModel: (_a2 = models[0]) != null ? _a2 : null
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
} catch (error) {
|
|
733
|
+
console.error("Failed to fetch models:", error);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
void fetchModels();
|
|
737
|
+
}, []);
|
|
738
|
+
const setApiKey = useCallback((apiKey) => {
|
|
739
|
+
setStateInternal((prev) => {
|
|
740
|
+
const next = __spreadProps(__spreadValues({}, prev), { apiKey: apiKey === "" ? null : apiKey });
|
|
741
|
+
callbacks.current.forEach((cb) => cb(next));
|
|
742
|
+
return next;
|
|
743
|
+
});
|
|
744
|
+
}, []);
|
|
745
|
+
const getAvailableModels = useCallback(async () => {
|
|
746
|
+
try {
|
|
747
|
+
const models = await backendApiRef.current.getModels(
|
|
748
|
+
sessionIdRef.current
|
|
749
|
+
);
|
|
750
|
+
setStateInternal((prev) => {
|
|
751
|
+
var _a2, _b2;
|
|
752
|
+
return __spreadProps(__spreadValues({}, prev), {
|
|
753
|
+
availableModels: models,
|
|
754
|
+
defaultModel: (_b2 = (_a2 = prev.defaultModel) != null ? _a2 : models[0]) != null ? _b2 : null
|
|
755
|
+
});
|
|
756
|
+
});
|
|
757
|
+
return models;
|
|
758
|
+
} catch (error) {
|
|
759
|
+
console.error("Failed to fetch models:", error);
|
|
760
|
+
return [];
|
|
761
|
+
}
|
|
762
|
+
}, []);
|
|
763
|
+
const getAuthorizedNamespaces = useCallback(async () => {
|
|
764
|
+
var _a2, _b2;
|
|
765
|
+
try {
|
|
766
|
+
const namespaces = await backendApiRef.current.getNamespaces(
|
|
767
|
+
sessionIdRef.current,
|
|
768
|
+
publicKeyRef.current,
|
|
769
|
+
(_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
|
|
770
|
+
);
|
|
771
|
+
const defaultNs = namespaces.includes("default") ? "default" : (_b2 = namespaces[0]) != null ? _b2 : null;
|
|
772
|
+
setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
773
|
+
authorizedNamespaces: namespaces,
|
|
774
|
+
defaultNamespace: defaultNs
|
|
775
|
+
}));
|
|
776
|
+
return namespaces;
|
|
777
|
+
} catch (error) {
|
|
778
|
+
console.error("Failed to fetch namespaces:", error);
|
|
779
|
+
setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
|
|
780
|
+
authorizedNamespaces: ["default"],
|
|
781
|
+
defaultNamespace: "default"
|
|
782
|
+
}));
|
|
783
|
+
return ["default"];
|
|
784
|
+
}
|
|
785
|
+
}, []);
|
|
786
|
+
const getCurrentThreadControl = useCallback(() => {
|
|
787
|
+
var _a2;
|
|
788
|
+
const metadata = getThreadMetadataRef.current(sessionIdRef.current);
|
|
789
|
+
return (_a2 = metadata == null ? void 0 : metadata.control) != null ? _a2 : initThreadControl();
|
|
790
|
+
}, []);
|
|
791
|
+
const onModelSelect = useCallback(async (model) => {
|
|
792
|
+
var _a2, _b2, _c, _d, _e;
|
|
793
|
+
const threadId = sessionIdRef.current;
|
|
794
|
+
const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
|
|
795
|
+
const isProcessing2 = currentControl.isProcessing;
|
|
796
|
+
console.log("[control-context] onModelSelect called", {
|
|
797
|
+
model,
|
|
798
|
+
isProcessing: isProcessing2,
|
|
799
|
+
threadId
|
|
800
|
+
});
|
|
801
|
+
if (isProcessing2) {
|
|
802
|
+
console.warn("[control-context] Cannot switch model while processing");
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const namespace = (_d = (_c = currentControl.namespace) != null ? _c : stateRef.current.defaultNamespace) != null ? _d : "default";
|
|
806
|
+
console.log("[control-context] onModelSelect updating metadata", {
|
|
807
|
+
threadId,
|
|
808
|
+
model,
|
|
809
|
+
namespace,
|
|
810
|
+
currentControl
|
|
811
|
+
});
|
|
812
|
+
updateThreadMetadataRef.current(threadId, {
|
|
813
|
+
control: __spreadProps(__spreadValues({}, currentControl), {
|
|
814
|
+
model,
|
|
815
|
+
namespace,
|
|
816
|
+
controlDirty: true
|
|
817
|
+
})
|
|
818
|
+
});
|
|
819
|
+
console.log("[control-context] onModelSelect calling backend setModel", {
|
|
820
|
+
threadId,
|
|
821
|
+
model,
|
|
822
|
+
namespace,
|
|
823
|
+
backendUrl: backendApiRef.current
|
|
824
|
+
});
|
|
825
|
+
try {
|
|
826
|
+
const result = await backendApiRef.current.setModel(
|
|
827
|
+
threadId,
|
|
828
|
+
model,
|
|
829
|
+
namespace,
|
|
830
|
+
(_e = stateRef.current.apiKey) != null ? _e : void 0
|
|
831
|
+
);
|
|
832
|
+
console.log("[control-context] onModelSelect backend result", result);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
console.error("[control-context] setModel failed:", err);
|
|
835
|
+
throw err;
|
|
836
|
+
}
|
|
837
|
+
}, []);
|
|
838
|
+
const onNamespaceSelect = useCallback((namespace) => {
|
|
839
|
+
var _a2, _b2;
|
|
840
|
+
const threadId = sessionIdRef.current;
|
|
841
|
+
const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
|
|
842
|
+
const isProcessing2 = currentControl.isProcessing;
|
|
843
|
+
console.log("[control-context] onNamespaceSelect called", {
|
|
844
|
+
namespace,
|
|
845
|
+
isProcessing: isProcessing2,
|
|
846
|
+
threadId
|
|
847
|
+
});
|
|
848
|
+
if (isProcessing2) {
|
|
849
|
+
console.warn(
|
|
850
|
+
"[control-context] Cannot switch namespace while processing"
|
|
851
|
+
);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
console.log("[control-context] onNamespaceSelect updating metadata", {
|
|
855
|
+
threadId,
|
|
856
|
+
namespace,
|
|
857
|
+
currentControl
|
|
858
|
+
});
|
|
859
|
+
updateThreadMetadataRef.current(threadId, {
|
|
860
|
+
control: __spreadProps(__spreadValues({}, currentControl), {
|
|
861
|
+
namespace,
|
|
862
|
+
controlDirty: true
|
|
863
|
+
})
|
|
864
|
+
});
|
|
865
|
+
console.log("[control-context] onNamespaceSelect metadata updated");
|
|
866
|
+
}, []);
|
|
867
|
+
const markControlSynced = useCallback(() => {
|
|
868
|
+
var _a2, _b2;
|
|
869
|
+
const threadId = sessionIdRef.current;
|
|
870
|
+
const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
|
|
871
|
+
if (currentControl.controlDirty) {
|
|
872
|
+
updateThreadMetadataRef.current(threadId, {
|
|
873
|
+
control: __spreadProps(__spreadValues({}, currentControl), {
|
|
874
|
+
controlDirty: false
|
|
875
|
+
})
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}, []);
|
|
879
|
+
const getControlState = useCallback(() => stateRef.current, []);
|
|
880
|
+
const onControlStateChange = useCallback(
|
|
881
|
+
(callback) => {
|
|
882
|
+
callbacks.current.add(callback);
|
|
883
|
+
return () => {
|
|
884
|
+
callbacks.current.delete(callback);
|
|
885
|
+
};
|
|
886
|
+
},
|
|
887
|
+
[]
|
|
888
|
+
);
|
|
889
|
+
const setState = useCallback(
|
|
890
|
+
(updates) => {
|
|
891
|
+
var _a2;
|
|
892
|
+
if ("apiKey" in updates) {
|
|
893
|
+
setApiKey((_a2 = updates.apiKey) != null ? _a2 : null);
|
|
894
|
+
}
|
|
895
|
+
if ("namespace" in updates && updates.namespace !== void 0 && updates.namespace !== null) {
|
|
896
|
+
onNamespaceSelect(updates.namespace);
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
[setApiKey, onNamespaceSelect]
|
|
900
|
+
);
|
|
901
|
+
return /* @__PURE__ */ jsx(
|
|
902
|
+
ControlContext.Provider,
|
|
903
|
+
{
|
|
904
|
+
value: {
|
|
905
|
+
state,
|
|
906
|
+
setApiKey,
|
|
907
|
+
getAvailableModels,
|
|
908
|
+
getAuthorizedNamespaces,
|
|
909
|
+
getCurrentThreadControl,
|
|
910
|
+
onModelSelect,
|
|
911
|
+
onNamespaceSelect,
|
|
912
|
+
isProcessing,
|
|
913
|
+
markControlSynced,
|
|
914
|
+
getControlState,
|
|
915
|
+
onControlStateChange,
|
|
916
|
+
setState
|
|
917
|
+
},
|
|
918
|
+
children
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// packages/react/src/contexts/event-context.tsx
|
|
924
|
+
import {
|
|
925
|
+
createContext as createContext2,
|
|
926
|
+
useCallback as useCallback2,
|
|
927
|
+
useContext as useContext2,
|
|
928
|
+
useEffect as useEffect2,
|
|
929
|
+
useRef as useRef2,
|
|
930
|
+
useState as useState2
|
|
931
|
+
} from "react";
|
|
932
|
+
|
|
933
|
+
// packages/react/src/backend/types.ts
|
|
934
|
+
function isInlineCall(event) {
|
|
935
|
+
return "InlineCall" in event;
|
|
936
|
+
}
|
|
937
|
+
function isSystemNotice(event) {
|
|
938
|
+
return "SystemNotice" in event;
|
|
939
|
+
}
|
|
940
|
+
function isSystemError(event) {
|
|
941
|
+
return "SystemError" in event;
|
|
942
|
+
}
|
|
943
|
+
function isAsyncCallback(event) {
|
|
944
|
+
return "AsyncCallback" in event;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// packages/react/src/state/event-buffer.ts
|
|
948
|
+
function createEventBuffer() {
|
|
949
|
+
return {
|
|
950
|
+
inboundQueue: [],
|
|
951
|
+
outboundQueue: [],
|
|
952
|
+
sseStatus: "disconnected",
|
|
953
|
+
lastEventId: null,
|
|
954
|
+
subscribers: /* @__PURE__ */ new Map()
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function enqueueInbound(state, event) {
|
|
958
|
+
state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
|
|
959
|
+
status: "pending",
|
|
960
|
+
timestamp: Date.now()
|
|
961
|
+
}));
|
|
962
|
+
}
|
|
963
|
+
function subscribe(state, type, callback) {
|
|
964
|
+
if (!state.subscribers.has(type)) {
|
|
965
|
+
state.subscribers.set(type, /* @__PURE__ */ new Set());
|
|
966
|
+
}
|
|
967
|
+
state.subscribers.get(type).add(callback);
|
|
968
|
+
return () => {
|
|
969
|
+
var _a;
|
|
970
|
+
(_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function dispatch(state, event) {
|
|
974
|
+
const typeSubscribers = state.subscribers.get(event.type);
|
|
975
|
+
if (typeSubscribers) {
|
|
976
|
+
for (const callback of typeSubscribers) {
|
|
977
|
+
callback(event);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const allSubscribers = state.subscribers.get("*");
|
|
981
|
+
if (allSubscribers) {
|
|
982
|
+
for (const callback of allSubscribers) {
|
|
983
|
+
callback(event);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
function setSSEStatus(state, status) {
|
|
988
|
+
state.sseStatus = status;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// packages/react/src/contexts/event-context.tsx
|
|
992
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
993
|
+
var EventContextState = createContext2(null);
|
|
994
|
+
function useEventContext() {
|
|
995
|
+
const context = useContext2(EventContextState);
|
|
996
|
+
if (!context) {
|
|
997
|
+
throw new Error(
|
|
998
|
+
"useEventContext must be used within EventContextProvider. Wrap your app with <EventContextProvider>...</EventContextProvider>"
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
return context;
|
|
1002
|
+
}
|
|
1003
|
+
function EventContextProvider({
|
|
1004
|
+
children,
|
|
1005
|
+
backendApi,
|
|
1006
|
+
sessionId
|
|
1007
|
+
}) {
|
|
1008
|
+
const bufferRef = useRef2(null);
|
|
1009
|
+
if (!bufferRef.current) {
|
|
1010
|
+
bufferRef.current = createEventBuffer();
|
|
1011
|
+
}
|
|
1012
|
+
const buffer = bufferRef.current;
|
|
1013
|
+
const [sseStatus, setSseStatus] = useState2("disconnected");
|
|
1014
|
+
useEffect2(() => {
|
|
1015
|
+
setSSEStatus(buffer, "connecting");
|
|
1016
|
+
setSseStatus("connecting");
|
|
1017
|
+
const unsubscribe = backendApi.subscribeSSE(
|
|
1018
|
+
sessionId,
|
|
1019
|
+
(event) => {
|
|
1020
|
+
enqueueInbound(buffer, {
|
|
1021
|
+
type: event.type,
|
|
1022
|
+
sessionId: event.session_id,
|
|
1023
|
+
payload: event
|
|
1024
|
+
});
|
|
1025
|
+
const inboundEvent = {
|
|
1026
|
+
type: event.type,
|
|
1027
|
+
sessionId: event.session_id,
|
|
1028
|
+
payload: event,
|
|
1029
|
+
status: "fetched",
|
|
1030
|
+
timestamp: Date.now()
|
|
1031
|
+
};
|
|
1032
|
+
dispatch(buffer, inboundEvent);
|
|
1033
|
+
},
|
|
1034
|
+
(error) => {
|
|
1035
|
+
console.error("SSE error:", error);
|
|
1036
|
+
setSSEStatus(buffer, "disconnected");
|
|
1037
|
+
setSseStatus("disconnected");
|
|
1038
|
+
}
|
|
1039
|
+
);
|
|
1040
|
+
setSSEStatus(buffer, "connected");
|
|
1041
|
+
setSseStatus("connected");
|
|
1042
|
+
return () => {
|
|
1043
|
+
unsubscribe();
|
|
1044
|
+
setSSEStatus(buffer, "disconnected");
|
|
1045
|
+
setSseStatus("disconnected");
|
|
1046
|
+
};
|
|
1047
|
+
}, [backendApi, sessionId, buffer]);
|
|
1048
|
+
const subscribeCallback = useCallback2(
|
|
1049
|
+
(type, callback) => {
|
|
1050
|
+
return subscribe(buffer, type, callback);
|
|
1051
|
+
},
|
|
1052
|
+
[buffer]
|
|
1053
|
+
);
|
|
1054
|
+
const sendOutbound = useCallback2(
|
|
1055
|
+
async (event) => {
|
|
1056
|
+
try {
|
|
1057
|
+
const message = JSON.stringify({
|
|
1058
|
+
type: event.type,
|
|
1059
|
+
payload: event.payload
|
|
1060
|
+
});
|
|
1061
|
+
await backendApi.postSystemMessage(event.sessionId, message);
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
console.error("Failed to send outbound event:", error);
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
[backendApi]
|
|
1067
|
+
);
|
|
1068
|
+
const dispatchSystemEvents = useCallback2(
|
|
1069
|
+
(sessionId2, events) => {
|
|
1070
|
+
var _a;
|
|
1071
|
+
for (const event of events) {
|
|
1072
|
+
let eventType;
|
|
1073
|
+
let payload;
|
|
1074
|
+
if (isInlineCall(event)) {
|
|
1075
|
+
eventType = event.InlineCall.type;
|
|
1076
|
+
payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
|
|
1077
|
+
} else if (isSystemNotice(event)) {
|
|
1078
|
+
eventType = "system_notice";
|
|
1079
|
+
payload = { message: event.SystemNotice };
|
|
1080
|
+
} else if (isSystemError(event)) {
|
|
1081
|
+
eventType = "system_error";
|
|
1082
|
+
payload = { message: event.SystemError };
|
|
1083
|
+
} else if (isAsyncCallback(event)) {
|
|
1084
|
+
eventType = "async_callback";
|
|
1085
|
+
payload = event.AsyncCallback;
|
|
1086
|
+
} else {
|
|
1087
|
+
console.warn("Unknown system event type:", event);
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
const inboundEvent = {
|
|
1091
|
+
type: eventType,
|
|
1092
|
+
sessionId: sessionId2,
|
|
1093
|
+
payload,
|
|
1094
|
+
status: "fetched",
|
|
1095
|
+
timestamp: Date.now()
|
|
1096
|
+
};
|
|
1097
|
+
enqueueInbound(buffer, {
|
|
1098
|
+
type: eventType,
|
|
1099
|
+
sessionId: sessionId2,
|
|
1100
|
+
payload
|
|
1101
|
+
});
|
|
1102
|
+
dispatch(buffer, inboundEvent);
|
|
1103
|
+
}
|
|
1104
|
+
},
|
|
1105
|
+
[buffer]
|
|
1106
|
+
);
|
|
1107
|
+
const contextValue = {
|
|
1108
|
+
subscribe: subscribeCallback,
|
|
1109
|
+
sendOutboundSystem: sendOutbound,
|
|
1110
|
+
dispatchInboundSystem: dispatchSystemEvents,
|
|
1111
|
+
sseStatus
|
|
1112
|
+
};
|
|
1113
|
+
return /* @__PURE__ */ jsx2(EventContextState.Provider, { value: contextValue, children });
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// packages/react/src/contexts/notification-context.tsx
|
|
1117
|
+
import {
|
|
1118
|
+
createContext as createContext3,
|
|
1119
|
+
useCallback as useCallback3,
|
|
1120
|
+
useContext as useContext3,
|
|
1121
|
+
useState as useState3
|
|
1122
|
+
} from "react";
|
|
1123
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1124
|
+
var NotificationContext = createContext3(null);
|
|
1125
|
+
function useNotification() {
|
|
1126
|
+
const context = useContext3(NotificationContext);
|
|
1127
|
+
if (!context) {
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
"useNotification must be used within NotificationContextProvider"
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return context;
|
|
1133
|
+
}
|
|
1134
|
+
var notificationIdCounter = 0;
|
|
1135
|
+
function generateId() {
|
|
1136
|
+
return `notif-${Date.now()}-${++notificationIdCounter}`;
|
|
1137
|
+
}
|
|
1138
|
+
function NotificationContextProvider({
|
|
1139
|
+
children
|
|
1140
|
+
}) {
|
|
1141
|
+
const [notifications, setNotifications] = useState3([]);
|
|
1142
|
+
const showNotification = useCallback3((params) => {
|
|
1143
|
+
const id = generateId();
|
|
1144
|
+
const notification = __spreadProps(__spreadValues({}, params), {
|
|
1145
|
+
id,
|
|
1146
|
+
timestamp: Date.now()
|
|
1147
|
+
});
|
|
1148
|
+
setNotifications((prev) => [notification, ...prev]);
|
|
1149
|
+
return id;
|
|
1150
|
+
}, []);
|
|
1151
|
+
const dismissNotification = useCallback3((id) => {
|
|
1152
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
1153
|
+
}, []);
|
|
1154
|
+
const clearAll = useCallback3(() => {
|
|
1155
|
+
setNotifications([]);
|
|
1156
|
+
}, []);
|
|
1157
|
+
const value = {
|
|
1158
|
+
notifications,
|
|
1159
|
+
showNotification,
|
|
1160
|
+
dismissNotification,
|
|
1161
|
+
clearAll
|
|
1162
|
+
};
|
|
1163
|
+
return /* @__PURE__ */ jsx3(NotificationContext.Provider, { value, children });
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// packages/react/src/contexts/thread-context.tsx
|
|
1167
|
+
import {
|
|
1168
|
+
createContext as createContext4,
|
|
1169
|
+
useContext as useContext4,
|
|
1170
|
+
useMemo,
|
|
1171
|
+
useRef as useRef3,
|
|
1172
|
+
useSyncExternalStore
|
|
1173
|
+
} from "react";
|
|
1174
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1175
|
+
var ThreadContextState = createContext4(null);
|
|
1176
|
+
function useThreadContext() {
|
|
1177
|
+
const context = useContext4(ThreadContextState);
|
|
1178
|
+
if (!context) {
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
"useThreadContext must be used within ThreadContextProvider. Wrap your app with <ThreadContextProvider>...</ThreadContextProvider>"
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
return context;
|
|
1184
|
+
}
|
|
1185
|
+
function ThreadContextProvider({
|
|
1186
|
+
children,
|
|
1187
|
+
initialThreadId
|
|
1188
|
+
}) {
|
|
1189
|
+
const storeRef = useRef3(null);
|
|
1190
|
+
if (!storeRef.current) {
|
|
1191
|
+
storeRef.current = new ThreadStore({ initialThreadId });
|
|
1192
|
+
}
|
|
1193
|
+
const store = storeRef.current;
|
|
1194
|
+
const value = useSyncExternalStore(
|
|
1195
|
+
store.subscribe,
|
|
1196
|
+
store.getSnapshot,
|
|
1197
|
+
store.getSnapshot
|
|
1198
|
+
);
|
|
1199
|
+
return /* @__PURE__ */ jsx4(ThreadContextState.Provider, { value, children });
|
|
1200
|
+
}
|
|
1201
|
+
function useCurrentThreadMessages() {
|
|
1202
|
+
const { currentThreadId, getThreadMessages } = useThreadContext();
|
|
1203
|
+
return useMemo(
|
|
1204
|
+
() => getThreadMessages(currentThreadId),
|
|
1205
|
+
[currentThreadId, getThreadMessages]
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
function useCurrentThreadMetadata() {
|
|
1209
|
+
const { currentThreadId, getThreadMetadata } = useThreadContext();
|
|
1210
|
+
return useMemo(
|
|
1211
|
+
() => getThreadMetadata(currentThreadId),
|
|
1212
|
+
[currentThreadId, getThreadMetadata]
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// packages/react/src/contexts/user-context.tsx
|
|
1217
|
+
import {
|
|
1218
|
+
createContext as createContext5,
|
|
1219
|
+
useCallback as useCallback4,
|
|
1220
|
+
useContext as useContext5,
|
|
1221
|
+
useRef as useRef4,
|
|
1222
|
+
useState as useState4
|
|
1223
|
+
} from "react";
|
|
1224
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1225
|
+
var UserContext = createContext5(void 0);
|
|
1226
|
+
function useUser() {
|
|
1227
|
+
const context = useContext5(UserContext);
|
|
1228
|
+
if (!context) {
|
|
1229
|
+
throw new Error("useUser must be used within UserContextProvider");
|
|
1230
|
+
}
|
|
1231
|
+
return {
|
|
1232
|
+
user: context.user,
|
|
1233
|
+
setUser: context.setUser,
|
|
1234
|
+
getUserState: context.getUserState,
|
|
1235
|
+
onUserStateChange: context.onUserStateChange
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function UserContextProvider({ children }) {
|
|
1239
|
+
const [user, setUserState] = useState4({
|
|
1240
|
+
isConnected: false,
|
|
1241
|
+
address: void 0,
|
|
1242
|
+
chainId: void 0,
|
|
1243
|
+
ensName: void 0
|
|
1244
|
+
});
|
|
1245
|
+
const userRef = useRef4(user);
|
|
1246
|
+
userRef.current = user;
|
|
1247
|
+
const StateChangeCallbacks = useRef4(
|
|
1248
|
+
/* @__PURE__ */ new Set()
|
|
1249
|
+
);
|
|
1250
|
+
const setUser = useCallback4((data) => {
|
|
1251
|
+
setUserState((prev) => {
|
|
1252
|
+
const next = __spreadValues(__spreadValues({}, prev), data);
|
|
1253
|
+
StateChangeCallbacks.current.forEach((callback) => {
|
|
1254
|
+
callback(next);
|
|
1255
|
+
});
|
|
1256
|
+
return next;
|
|
1257
|
+
});
|
|
1258
|
+
}, []);
|
|
1259
|
+
const getUserState = useCallback4(() => userRef.current, []);
|
|
1260
|
+
const onUserStateChange = useCallback4(
|
|
1261
|
+
(callback) => {
|
|
1262
|
+
StateChangeCallbacks.current.add(callback);
|
|
1263
|
+
return () => {
|
|
1264
|
+
StateChangeCallbacks.current.delete(callback);
|
|
1265
|
+
};
|
|
1266
|
+
},
|
|
1267
|
+
[]
|
|
1268
|
+
);
|
|
1269
|
+
return /* @__PURE__ */ jsx5(
|
|
1270
|
+
UserContext.Provider,
|
|
1271
|
+
{
|
|
1272
|
+
value: {
|
|
1273
|
+
user,
|
|
1274
|
+
setUser,
|
|
1275
|
+
getUserState,
|
|
1276
|
+
onUserStateChange
|
|
1277
|
+
},
|
|
1278
|
+
children
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// packages/react/src/runtime/core.tsx
|
|
1284
|
+
import { useCallback as useCallback6, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef6 } from "react";
|
|
1285
|
+
import {
|
|
1286
|
+
AssistantRuntimeProvider,
|
|
1287
|
+
useExternalStoreRuntime
|
|
1288
|
+
} from "@assistant-ui/react";
|
|
1289
|
+
|
|
1290
|
+
// packages/react/src/runtime/orchestrator.ts
|
|
1291
|
+
import { useCallback as useCallback5, useRef as useRef5, useState as useState5 } from "react";
|
|
1292
|
+
|
|
1293
|
+
// packages/react/src/runtime/utils.ts
|
|
1294
|
+
import { clsx } from "clsx";
|
|
1295
|
+
import { twMerge } from "tailwind-merge";
|
|
1296
|
+
function cn(...inputs) {
|
|
1297
|
+
return twMerge(clsx(inputs));
|
|
1298
|
+
}
|
|
1299
|
+
var parseTimestamp = (value) => {
|
|
1300
|
+
if (value === void 0 || value === null) return 0;
|
|
1301
|
+
if (typeof value === "number") {
|
|
1302
|
+
return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
|
|
1303
|
+
}
|
|
1304
|
+
const numeric = Number(value);
|
|
1305
|
+
if (!Number.isNaN(numeric)) {
|
|
1306
|
+
return numeric < 1e12 ? numeric * 1e3 : numeric;
|
|
1307
|
+
}
|
|
1308
|
+
const ts = Date.parse(value);
|
|
1309
|
+
return Number.isNaN(ts) ? 0 : ts;
|
|
1310
|
+
};
|
|
1311
|
+
var isPlaceholderTitle = (title) => {
|
|
1312
|
+
var _a;
|
|
1313
|
+
const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
|
|
1314
|
+
return !normalized || normalized.startsWith("#[");
|
|
1315
|
+
};
|
|
1316
|
+
function toInboundMessage(msg) {
|
|
1317
|
+
var _a;
|
|
1318
|
+
if (msg.sender === "system") return null;
|
|
1319
|
+
const content = [];
|
|
1320
|
+
const role = msg.sender === "user" ? "user" : "assistant";
|
|
1321
|
+
if (msg.content) {
|
|
1322
|
+
content.push({ type: "text", text: msg.content });
|
|
1323
|
+
}
|
|
1324
|
+
const [topic, toolContent] = (_a = parseToolPayload(msg)) != null ? _a : [];
|
|
1325
|
+
if (topic && toolContent) {
|
|
1326
|
+
content.push({
|
|
1327
|
+
type: "tool-call",
|
|
1328
|
+
toolCallId: `tool_${Date.now()}`,
|
|
1329
|
+
toolName: topic,
|
|
1330
|
+
args: void 0,
|
|
1331
|
+
result: (() => {
|
|
1332
|
+
try {
|
|
1333
|
+
return JSON.parse(toolContent);
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
return { args: toolContent };
|
|
1336
|
+
}
|
|
1337
|
+
})()
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
const threadMessage = __spreadValues({
|
|
1341
|
+
role,
|
|
1342
|
+
content: content.length > 0 ? content : [{ type: "text", text: "" }]
|
|
1343
|
+
}, msg.timestamp && { createdAt: new Date(msg.timestamp) });
|
|
1344
|
+
return threadMessage;
|
|
1345
|
+
}
|
|
1346
|
+
function parseToolPayload(msg) {
|
|
1347
|
+
return parseToolResult(msg.tool_result);
|
|
1348
|
+
}
|
|
1349
|
+
function parseToolResult(toolResult) {
|
|
1350
|
+
if (!toolResult) return null;
|
|
1351
|
+
if (Array.isArray(toolResult) && toolResult.length === 2) {
|
|
1352
|
+
const [topic, content] = toolResult;
|
|
1353
|
+
return [String(topic), String(content != null ? content : "")];
|
|
1354
|
+
}
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
var getNetworkName = (chainId) => {
|
|
1358
|
+
if (chainId === void 0) return "";
|
|
1359
|
+
const id = typeof chainId === "string" ? Number(chainId) : chainId;
|
|
1360
|
+
switch (id) {
|
|
1361
|
+
case 1:
|
|
1362
|
+
return "ethereum";
|
|
1363
|
+
case 137:
|
|
1364
|
+
return "polygon";
|
|
1365
|
+
case 42161:
|
|
1366
|
+
return "arbitrum";
|
|
1367
|
+
case 8453:
|
|
1368
|
+
return "base";
|
|
1369
|
+
case 10:
|
|
1370
|
+
return "optimism";
|
|
1371
|
+
case 11155111:
|
|
1372
|
+
return "sepolia";
|
|
1373
|
+
case 1337:
|
|
1374
|
+
case 31337:
|
|
1375
|
+
return "testnet";
|
|
1376
|
+
case 59140:
|
|
1377
|
+
return "linea-sepolia";
|
|
1378
|
+
case 59144:
|
|
1379
|
+
return "linea";
|
|
1380
|
+
default:
|
|
1381
|
+
return "testnet";
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
|
|
1385
|
+
|
|
1386
|
+
// packages/react/src/state/backend-state.ts
|
|
1387
|
+
function createBackendState() {
|
|
1388
|
+
return {
|
|
1389
|
+
skipInitialFetch: /* @__PURE__ */ new Set(),
|
|
1390
|
+
pendingChat: /* @__PURE__ */ new Map(),
|
|
1391
|
+
runningThreads: /* @__PURE__ */ new Set(),
|
|
1392
|
+
creatingThreadId: null,
|
|
1393
|
+
createThreadPromise: null
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function resolveThreadId(state, threadId) {
|
|
1397
|
+
return threadId;
|
|
1398
|
+
}
|
|
1399
|
+
function isThreadReady(state, threadId) {
|
|
1400
|
+
return state.creatingThreadId !== threadId;
|
|
1401
|
+
}
|
|
1402
|
+
function markSkipInitialFetch(state, threadId) {
|
|
1403
|
+
state.skipInitialFetch.add(threadId);
|
|
1404
|
+
}
|
|
1405
|
+
function shouldSkipInitialFetch(state, threadId) {
|
|
1406
|
+
return state.skipInitialFetch.has(threadId);
|
|
1407
|
+
}
|
|
1408
|
+
function clearSkipInitialFetch(state, threadId) {
|
|
1409
|
+
state.skipInitialFetch.delete(threadId);
|
|
1410
|
+
}
|
|
1411
|
+
function setThreadRunning(state, threadId, running) {
|
|
1412
|
+
if (running) {
|
|
1413
|
+
state.runningThreads.add(threadId);
|
|
1414
|
+
} else {
|
|
1415
|
+
state.runningThreads.delete(threadId);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function isThreadRunning(state, threadId) {
|
|
1419
|
+
return state.runningThreads.has(threadId);
|
|
1420
|
+
}
|
|
1421
|
+
function enqueuePendingChat(state, threadId, text) {
|
|
1422
|
+
var _a;
|
|
1423
|
+
const existing = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
|
|
1424
|
+
state.pendingChat.set(threadId, [...existing, text]);
|
|
1425
|
+
}
|
|
1426
|
+
function dequeuePendingChat(state, threadId) {
|
|
1427
|
+
var _a;
|
|
1428
|
+
const pending = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
|
|
1429
|
+
state.pendingChat.delete(threadId);
|
|
1430
|
+
return pending;
|
|
1431
|
+
}
|
|
1432
|
+
function hasPendingChat(state, threadId) {
|
|
1433
|
+
var _a, _b;
|
|
1434
|
+
return ((_b = (_a = state.pendingChat.get(threadId)) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// packages/react/src/runtime/message-controller.ts
|
|
1438
|
+
var MessageController = class {
|
|
1439
|
+
constructor(config) {
|
|
1440
|
+
this.config = config;
|
|
1441
|
+
}
|
|
1442
|
+
inbound(threadId, msgs) {
|
|
1443
|
+
const backendState = this.config.backendStateRef.current;
|
|
1444
|
+
if (!msgs) return;
|
|
1445
|
+
if (hasPendingChat(backendState, threadId)) {
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const threadMessages = [];
|
|
1449
|
+
for (const msg of msgs) {
|
|
1450
|
+
const threadMessage = toInboundMessage(msg);
|
|
1451
|
+
if (threadMessage) {
|
|
1452
|
+
threadMessages.push(threadMessage);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
|
|
1456
|
+
}
|
|
1457
|
+
async outbound(message, threadId) {
|
|
1458
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1459
|
+
const backendState = this.config.backendStateRef.current;
|
|
1460
|
+
const text = message.content.filter(
|
|
1461
|
+
(part) => part.type === "text"
|
|
1462
|
+
).map(
|
|
1463
|
+
(part) => part.text
|
|
1464
|
+
).join("\n");
|
|
1465
|
+
if (!text) return;
|
|
1466
|
+
const threadState = this.getThreadContextApi();
|
|
1467
|
+
const existingMessages = threadState.getThreadMessages(threadId);
|
|
1468
|
+
const userMessage = {
|
|
1469
|
+
role: "user",
|
|
1470
|
+
content: [{ type: "text", text }],
|
|
1471
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1472
|
+
};
|
|
1473
|
+
threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
|
|
1474
|
+
threadState.updateThreadMetadata(threadId, {
|
|
1475
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1476
|
+
});
|
|
1477
|
+
if (!isThreadReady(backendState, threadId)) {
|
|
1478
|
+
this.markRunning(threadId, true);
|
|
1479
|
+
enqueuePendingChat(backendState, threadId, text);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1483
|
+
const namespace = this.config.getNamespace();
|
|
1484
|
+
const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
|
|
1485
|
+
const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
|
|
1486
|
+
try {
|
|
1487
|
+
this.markRunning(threadId, true);
|
|
1488
|
+
const response = await this.config.backendApiRef.current.postChatMessage(
|
|
1489
|
+
backendThreadId,
|
|
1490
|
+
text,
|
|
1491
|
+
namespace,
|
|
1492
|
+
publicKey,
|
|
1493
|
+
apiKey
|
|
1494
|
+
);
|
|
1495
|
+
if (response == null ? void 0 : response.messages) {
|
|
1496
|
+
this.inbound(threadId, response.messages);
|
|
1497
|
+
}
|
|
1498
|
+
if (((_f = response == null ? void 0 : response.system_events) == null ? void 0 : _f.length) && this.config.onSyncEvents) {
|
|
1499
|
+
this.config.onSyncEvents(backendThreadId, response.system_events);
|
|
1500
|
+
}
|
|
1501
|
+
if (response == null ? void 0 : response.is_processing) {
|
|
1502
|
+
this.config.polling.start(threadId);
|
|
1503
|
+
} else if (!this.config.polling.isPolling(threadId)) {
|
|
1504
|
+
this.markRunning(threadId, false);
|
|
1505
|
+
}
|
|
1506
|
+
} catch (error) {
|
|
1507
|
+
console.error("Failed to send message:", error);
|
|
1508
|
+
this.markRunning(threadId, false);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async flushPendingChat(threadId) {
|
|
1512
|
+
var _a, _b, _c, _d, _e;
|
|
1513
|
+
const backendState = this.config.backendStateRef.current;
|
|
1514
|
+
const pending = dequeuePendingChat(backendState, threadId);
|
|
1515
|
+
if (!pending.length) return;
|
|
1516
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1517
|
+
const namespace = this.config.getNamespace();
|
|
1518
|
+
const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
|
|
1519
|
+
const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
|
|
1520
|
+
for (const text of pending) {
|
|
1521
|
+
try {
|
|
1522
|
+
await this.config.backendApiRef.current.postChatMessage(
|
|
1523
|
+
backendThreadId,
|
|
1524
|
+
text,
|
|
1525
|
+
namespace,
|
|
1526
|
+
publicKey,
|
|
1527
|
+
apiKey
|
|
1528
|
+
);
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
console.error("Failed to send queued message:", error);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
this.config.polling.start(threadId);
|
|
1534
|
+
}
|
|
1535
|
+
async cancel(threadId) {
|
|
1536
|
+
var _a;
|
|
1537
|
+
const backendState = this.config.backendStateRef.current;
|
|
1538
|
+
if (!isThreadReady(backendState, threadId)) return;
|
|
1539
|
+
this.config.polling.stop(threadId);
|
|
1540
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1541
|
+
try {
|
|
1542
|
+
const response = await this.config.backendApiRef.current.postInterrupt(backendThreadId);
|
|
1543
|
+
if (response == null ? void 0 : response.messages) {
|
|
1544
|
+
this.inbound(threadId, response.messages);
|
|
1545
|
+
}
|
|
1546
|
+
if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
|
|
1547
|
+
this.config.onSyncEvents(backendThreadId, response.system_events);
|
|
1548
|
+
}
|
|
1549
|
+
this.markRunning(threadId, false);
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
console.error("Failed to cancel:", error);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
markRunning(threadId, running) {
|
|
1555
|
+
var _a, _b;
|
|
1556
|
+
setThreadRunning(this.config.backendStateRef.current, threadId, running);
|
|
1557
|
+
if (this.config.threadContextRef.current.currentThreadId === threadId) {
|
|
1558
|
+
(_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
getThreadContextApi() {
|
|
1562
|
+
const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
|
|
1563
|
+
return { getThreadMessages, setThreadMessages, updateThreadMetadata };
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
|
|
1567
|
+
// packages/react/src/runtime/polling-controller.ts
|
|
1568
|
+
var PollingController = class {
|
|
1569
|
+
constructor(config) {
|
|
1570
|
+
this.config = config;
|
|
1571
|
+
this.intervals = /* @__PURE__ */ new Map();
|
|
1572
|
+
var _a;
|
|
1573
|
+
this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
|
|
1574
|
+
}
|
|
1575
|
+
start(threadId) {
|
|
1576
|
+
var _a, _b;
|
|
1577
|
+
const backendState = this.config.backendStateRef.current;
|
|
1578
|
+
if (!isThreadReady(backendState, threadId)) return;
|
|
1579
|
+
if (this.intervals.has(threadId)) return;
|
|
1580
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1581
|
+
setThreadRunning(backendState, threadId, true);
|
|
1582
|
+
const tick = async () => {
|
|
1583
|
+
var _a2, _b2;
|
|
1584
|
+
if (!this.intervals.has(threadId)) return;
|
|
1585
|
+
try {
|
|
1586
|
+
console.log(
|
|
1587
|
+
"[PollingController] Fetching state for threadId:",
|
|
1588
|
+
threadId
|
|
1589
|
+
);
|
|
1590
|
+
const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
|
|
1591
|
+
const state = await this.config.backendApiRef.current.fetchState(
|
|
1592
|
+
backendThreadId,
|
|
1593
|
+
userState
|
|
1594
|
+
);
|
|
1595
|
+
if (!this.intervals.has(threadId)) return;
|
|
1596
|
+
this.handleState(threadId, state);
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
console.error("Polling error:", error);
|
|
1599
|
+
this.stop(threadId);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
const intervalId = setInterval(tick, this.intervalMs);
|
|
1603
|
+
this.intervals.set(threadId, intervalId);
|
|
1604
|
+
(_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
|
|
1605
|
+
}
|
|
1606
|
+
stop(threadId) {
|
|
1607
|
+
var _a, _b;
|
|
1608
|
+
const intervalId = this.intervals.get(threadId);
|
|
1609
|
+
if (intervalId) {
|
|
1610
|
+
clearInterval(intervalId);
|
|
1611
|
+
this.intervals.delete(threadId);
|
|
1612
|
+
}
|
|
1613
|
+
setThreadRunning(this.config.backendStateRef.current, threadId, false);
|
|
1614
|
+
(_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
|
|
1615
|
+
}
|
|
1616
|
+
isPolling(threadId) {
|
|
1617
|
+
return this.intervals.has(threadId);
|
|
1618
|
+
}
|
|
1619
|
+
stopAll() {
|
|
1620
|
+
for (const threadId of this.intervals.keys()) {
|
|
1621
|
+
this.stop(threadId);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
handleState(threadId, state) {
|
|
1625
|
+
var _a;
|
|
1626
|
+
if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
|
|
1627
|
+
const backendState = this.config.backendStateRef.current;
|
|
1628
|
+
const sessionId = resolveThreadId(backendState, threadId);
|
|
1629
|
+
this.config.onSyncEvents(sessionId, state.system_events);
|
|
1630
|
+
}
|
|
1631
|
+
this.config.applyMessages(threadId, state.messages);
|
|
1632
|
+
if (!state.is_processing) {
|
|
1633
|
+
this.stop(threadId);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1638
|
+
// packages/react/src/runtime/orchestrator.ts
|
|
1639
|
+
function useRuntimeOrchestrator(backendApi, options) {
|
|
1640
|
+
const threadContext = useThreadContext();
|
|
1641
|
+
const threadContextRef = useRef5(threadContext);
|
|
1642
|
+
threadContextRef.current = threadContext;
|
|
1643
|
+
const backendApiRef = useRef5(backendApi);
|
|
1644
|
+
backendApiRef.current = backendApi;
|
|
1645
|
+
const backendStateRef = useRef5(createBackendState());
|
|
1646
|
+
const [isRunning, setIsRunning] = useState5(false);
|
|
1647
|
+
const messageControllerRef = useRef5(null);
|
|
1648
|
+
const pollingRef = useRef5(null);
|
|
1649
|
+
const pendingFetches = useRef5(/* @__PURE__ */ new Set());
|
|
1650
|
+
if (!pollingRef.current) {
|
|
1651
|
+
pollingRef.current = new PollingController({
|
|
1652
|
+
backendApiRef,
|
|
1653
|
+
backendStateRef,
|
|
1654
|
+
applyMessages: (threadId, msgs) => {
|
|
1655
|
+
var _a;
|
|
1656
|
+
(_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
|
|
1657
|
+
},
|
|
1658
|
+
onSyncEvents: options.onSyncEvents,
|
|
1659
|
+
getUserState: options.getUserState,
|
|
1660
|
+
onStart: (threadId) => {
|
|
1661
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1662
|
+
setIsRunning(true);
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
onStop: (threadId) => {
|
|
1666
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1667
|
+
setIsRunning(false);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
if (!messageControllerRef.current) {
|
|
1673
|
+
messageControllerRef.current = new MessageController({
|
|
1674
|
+
backendApiRef,
|
|
1675
|
+
backendStateRef,
|
|
1676
|
+
threadContextRef,
|
|
1677
|
+
polling: pollingRef.current,
|
|
1678
|
+
setGlobalIsRunning: setIsRunning,
|
|
1679
|
+
getPublicKey: options.getPublicKey,
|
|
1680
|
+
getNamespace: options.getNamespace,
|
|
1681
|
+
getApiKey: options.getApiKey,
|
|
1682
|
+
onSyncEvents: options.onSyncEvents
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
const ensureInitialState = useCallback5(async (threadId) => {
|
|
1686
|
+
var _a, _b, _c, _d;
|
|
1687
|
+
const backendState = backendStateRef.current;
|
|
1688
|
+
if (shouldSkipInitialFetch(backendState, threadId)) {
|
|
1689
|
+
clearSkipInitialFetch(backendState, threadId);
|
|
1690
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1691
|
+
setIsRunning(false);
|
|
1692
|
+
}
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (!isThreadReady(backendState, threadId)) {
|
|
1696
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1697
|
+
setIsRunning(false);
|
|
1698
|
+
}
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
if (pendingFetches.current.has(threadId)) return;
|
|
1702
|
+
const backendThreadId = resolveThreadId(backendState, threadId);
|
|
1703
|
+
pendingFetches.current.add(threadId);
|
|
1704
|
+
try {
|
|
1705
|
+
const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
|
|
1706
|
+
const state = await backendApiRef.current.fetchState(
|
|
1707
|
+
backendThreadId,
|
|
1708
|
+
userState
|
|
1709
|
+
);
|
|
1710
|
+
(_b = messageControllerRef.current) == null ? void 0 : _b.inbound(threadId, state.messages);
|
|
1711
|
+
if (((_c = state.system_events) == null ? void 0 : _c.length) && options.onSyncEvents) {
|
|
1712
|
+
options.onSyncEvents(backendThreadId, state.system_events);
|
|
1713
|
+
}
|
|
1714
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1715
|
+
if (state.is_processing) {
|
|
1716
|
+
setIsRunning(true);
|
|
1717
|
+
(_d = pollingRef.current) == null ? void 0 : _d.start(threadId);
|
|
1718
|
+
} else {
|
|
1719
|
+
setIsRunning(false);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
console.error("Failed to fetch initial state:", error);
|
|
1724
|
+
if (threadContextRef.current.currentThreadId === threadId) {
|
|
1725
|
+
setIsRunning(false);
|
|
1726
|
+
}
|
|
1727
|
+
} finally {
|
|
1728
|
+
pendingFetches.current.delete(threadId);
|
|
1729
|
+
}
|
|
1730
|
+
}, []);
|
|
1731
|
+
return {
|
|
1732
|
+
backendStateRef,
|
|
1733
|
+
polling: pollingRef.current,
|
|
1734
|
+
messageController: messageControllerRef.current,
|
|
1735
|
+
isRunning,
|
|
1736
|
+
setIsRunning,
|
|
1737
|
+
ensureInitialState,
|
|
1738
|
+
backendApiRef
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// packages/react/src/runtime/threadlist-adapter.ts
|
|
1743
|
+
var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
|
|
1744
|
+
const tsA = parseTimestamp(metaA.lastActiveAt);
|
|
1745
|
+
const tsB = parseTimestamp(metaB.lastActiveAt);
|
|
1746
|
+
return tsB - tsA;
|
|
1747
|
+
};
|
|
1748
|
+
function buildThreadLists(threadMetadata) {
|
|
1749
|
+
const entries = Array.from(threadMetadata.entries()).filter(
|
|
1750
|
+
([, meta]) => !isPlaceholderTitle(meta.title)
|
|
1751
|
+
);
|
|
1752
|
+
const regularThreads = entries.filter(([, meta]) => meta.status !== "archived").sort(sortByLastActiveDesc).map(
|
|
1753
|
+
([id, meta]) => ({
|
|
1754
|
+
id,
|
|
1755
|
+
title: meta.title || "New Chat",
|
|
1756
|
+
status: "regular"
|
|
1757
|
+
})
|
|
1758
|
+
);
|
|
1759
|
+
const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(
|
|
1760
|
+
([id, meta]) => ({
|
|
1761
|
+
id,
|
|
1762
|
+
title: meta.title || "New Chat",
|
|
1763
|
+
status: "archived"
|
|
1764
|
+
})
|
|
1765
|
+
);
|
|
1766
|
+
return { regularThreads, archivedThreads };
|
|
1767
|
+
}
|
|
1768
|
+
function buildThreadListAdapter({
|
|
1769
|
+
backendStateRef,
|
|
1770
|
+
backendApiRef,
|
|
1771
|
+
threadContext,
|
|
1772
|
+
currentThreadIdRef,
|
|
1773
|
+
polling,
|
|
1774
|
+
userAddress,
|
|
1775
|
+
setIsRunning,
|
|
1776
|
+
getNamespace,
|
|
1777
|
+
getApiKey
|
|
1778
|
+
}) {
|
|
1779
|
+
const backendState = backendStateRef.current;
|
|
1780
|
+
const { regularThreads, archivedThreads } = buildThreadLists(
|
|
1781
|
+
threadContext.allThreadsMetadata
|
|
1782
|
+
);
|
|
1783
|
+
const preparePendingThread = (threadId) => {
|
|
1784
|
+
const previousPendingId = backendState.creatingThreadId;
|
|
1785
|
+
if (previousPendingId && previousPendingId !== threadId) {
|
|
1786
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1787
|
+
const next = new Map(prev);
|
|
1788
|
+
next.delete(previousPendingId);
|
|
1789
|
+
return next;
|
|
1790
|
+
});
|
|
1791
|
+
threadContext.setThreads((prev) => {
|
|
1792
|
+
const next = new Map(prev);
|
|
1793
|
+
next.delete(previousPendingId);
|
|
1794
|
+
return next;
|
|
1795
|
+
});
|
|
1796
|
+
backendState.pendingChat.delete(previousPendingId);
|
|
1797
|
+
backendState.skipInitialFetch.delete(previousPendingId);
|
|
1798
|
+
}
|
|
1799
|
+
backendState.creatingThreadId = threadId;
|
|
1800
|
+
backendState.pendingChat.delete(threadId);
|
|
1801
|
+
threadContext.setThreadMetadata(
|
|
1802
|
+
(prev) => new Map(prev).set(threadId, {
|
|
1803
|
+
title: "New Chat",
|
|
1804
|
+
status: "pending",
|
|
1805
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1806
|
+
control: initThreadControl()
|
|
1807
|
+
})
|
|
1808
|
+
);
|
|
1809
|
+
threadContext.setThreadMessages(threadId, []);
|
|
1810
|
+
threadContext.setCurrentThreadId(threadId);
|
|
1811
|
+
setIsRunning(false);
|
|
1812
|
+
threadContext.bumpThreadViewKey();
|
|
1813
|
+
};
|
|
1814
|
+
const findPendingThreadId = () => {
|
|
1815
|
+
if (backendState.creatingThreadId) return backendState.creatingThreadId;
|
|
1816
|
+
for (const [id, meta] of threadContext.allThreadsMetadata.entries()) {
|
|
1817
|
+
if (meta.status === "pending") return id;
|
|
1818
|
+
}
|
|
1819
|
+
return null;
|
|
1820
|
+
};
|
|
1821
|
+
return {
|
|
1822
|
+
threadId: threadContext.currentThreadId,
|
|
1823
|
+
threads: regularThreads,
|
|
1824
|
+
archivedThreads,
|
|
1825
|
+
onSwitchToNewThread: async () => {
|
|
1826
|
+
var _a;
|
|
1827
|
+
const pendingId = findPendingThreadId();
|
|
1828
|
+
if (pendingId) {
|
|
1829
|
+
preparePendingThread(pendingId);
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (backendState.createThreadPromise) {
|
|
1833
|
+
preparePendingThread((_a = backendState.creatingThreadId) != null ? _a : generateUUID());
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
const threadId = generateUUID();
|
|
1837
|
+
preparePendingThread(threadId);
|
|
1838
|
+
const createPromise = backendApiRef.current.createThread(threadId, userAddress).then(async (newThread) => {
|
|
1839
|
+
var _a2, _b;
|
|
1840
|
+
const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
|
|
1841
|
+
const backendId = newThread.session_id;
|
|
1842
|
+
if (uiThreadId !== backendId) {
|
|
1843
|
+
console.warn("[aomi][thread] backend id mismatch", {
|
|
1844
|
+
uiThreadId,
|
|
1845
|
+
backendId
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
markSkipInitialFetch(backendState, uiThreadId);
|
|
1849
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1850
|
+
var _a3, _b2, _c;
|
|
1851
|
+
const next = new Map(prev);
|
|
1852
|
+
const existing = next.get(uiThreadId);
|
|
1853
|
+
const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
|
|
1854
|
+
next.set(uiThreadId, {
|
|
1855
|
+
title: (_a3 = existing == null ? void 0 : existing.title) != null ? _a3 : "New Chat",
|
|
1856
|
+
status: nextStatus,
|
|
1857
|
+
lastActiveAt: (_b2 = existing == null ? void 0 : existing.lastActiveAt) != null ? _b2 : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1858
|
+
control: (_c = existing == null ? void 0 : existing.control) != null ? _c : initThreadControl()
|
|
1859
|
+
});
|
|
1860
|
+
return next;
|
|
1861
|
+
});
|
|
1862
|
+
if (backendState.creatingThreadId === uiThreadId) {
|
|
1863
|
+
backendState.creatingThreadId = null;
|
|
1864
|
+
}
|
|
1865
|
+
const pendingMessages = backendState.pendingChat.get(uiThreadId);
|
|
1866
|
+
if (pendingMessages == null ? void 0 : pendingMessages.length) {
|
|
1867
|
+
backendState.pendingChat.delete(uiThreadId);
|
|
1868
|
+
const namespace = getNamespace();
|
|
1869
|
+
const apiKey = (_b = getApiKey == null ? void 0 : getApiKey()) != null ? _b : void 0;
|
|
1870
|
+
for (const text of pendingMessages) {
|
|
1871
|
+
try {
|
|
1872
|
+
await backendApiRef.current.postChatMessage(
|
|
1873
|
+
backendId,
|
|
1874
|
+
text,
|
|
1875
|
+
namespace,
|
|
1876
|
+
userAddress,
|
|
1877
|
+
apiKey
|
|
1878
|
+
);
|
|
1879
|
+
} catch (error) {
|
|
1880
|
+
console.error("Failed to send queued message:", error);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
if (currentThreadIdRef.current === uiThreadId) {
|
|
1884
|
+
polling == null ? void 0 : polling.start(uiThreadId);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}).catch((error) => {
|
|
1888
|
+
var _a2;
|
|
1889
|
+
console.error("Failed to create new thread:", error);
|
|
1890
|
+
const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
|
|
1891
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1892
|
+
const next = new Map(prev);
|
|
1893
|
+
next.delete(failedId);
|
|
1894
|
+
return next;
|
|
1895
|
+
});
|
|
1896
|
+
threadContext.setThreads((prev) => {
|
|
1897
|
+
const next = new Map(prev);
|
|
1898
|
+
next.delete(failedId);
|
|
1899
|
+
return next;
|
|
1900
|
+
});
|
|
1901
|
+
if (backendState.creatingThreadId === failedId) {
|
|
1902
|
+
backendState.creatingThreadId = null;
|
|
1903
|
+
}
|
|
1904
|
+
}).finally(() => {
|
|
1905
|
+
backendState.createThreadPromise = null;
|
|
1906
|
+
});
|
|
1907
|
+
backendState.createThreadPromise = createPromise;
|
|
1908
|
+
},
|
|
1909
|
+
onSwitchToThread: (threadId) => {
|
|
1910
|
+
threadContext.setCurrentThreadId(threadId);
|
|
1911
|
+
},
|
|
1912
|
+
onRename: async (threadId, newTitle) => {
|
|
1913
|
+
var _a, _b;
|
|
1914
|
+
const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
|
|
1915
|
+
const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
|
|
1916
|
+
threadContext.updateThreadMetadata(threadId, {
|
|
1917
|
+
title: normalizedTitle
|
|
1918
|
+
});
|
|
1919
|
+
try {
|
|
1920
|
+
await backendApiRef.current.renameThread(threadId, newTitle);
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
console.error("Failed to rename thread:", error);
|
|
1923
|
+
threadContext.updateThreadMetadata(threadId, {
|
|
1924
|
+
title: previousTitle
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
onArchive: async (threadId) => {
|
|
1929
|
+
threadContext.updateThreadMetadata(threadId, { status: "archived" });
|
|
1930
|
+
try {
|
|
1931
|
+
await backendApiRef.current.archiveThread(threadId);
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
console.error("Failed to archive thread:", error);
|
|
1934
|
+
threadContext.updateThreadMetadata(threadId, { status: "regular" });
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
onUnarchive: async (threadId) => {
|
|
1938
|
+
threadContext.updateThreadMetadata(threadId, { status: "regular" });
|
|
1939
|
+
try {
|
|
1940
|
+
await backendApiRef.current.unarchiveThread(threadId);
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
console.error("Failed to unarchive thread:", error);
|
|
1943
|
+
threadContext.updateThreadMetadata(threadId, { status: "archived" });
|
|
1944
|
+
}
|
|
1945
|
+
},
|
|
1946
|
+
onDelete: async (threadId) => {
|
|
1947
|
+
try {
|
|
1948
|
+
await backendApiRef.current.deleteThread(threadId);
|
|
1949
|
+
threadContext.setThreadMetadata((prev) => {
|
|
1950
|
+
const next = new Map(prev);
|
|
1951
|
+
next.delete(threadId);
|
|
1952
|
+
return next;
|
|
1953
|
+
});
|
|
1954
|
+
threadContext.setThreads((prev) => {
|
|
1955
|
+
const next = new Map(prev);
|
|
1956
|
+
next.delete(threadId);
|
|
1957
|
+
return next;
|
|
1958
|
+
});
|
|
1959
|
+
backendState.pendingChat.delete(threadId);
|
|
1960
|
+
backendState.skipInitialFetch.delete(threadId);
|
|
1961
|
+
backendState.runningThreads.delete(threadId);
|
|
1962
|
+
if (backendState.creatingThreadId === threadId) {
|
|
1963
|
+
backendState.creatingThreadId = null;
|
|
1964
|
+
}
|
|
1965
|
+
if (threadContext.currentThreadId === threadId) {
|
|
1966
|
+
const firstRegularThread = Array.from(
|
|
1967
|
+
threadContext.allThreadsMetadata.entries()
|
|
1968
|
+
).find(([id, meta]) => meta.status === "regular" && id !== threadId);
|
|
1969
|
+
if (firstRegularThread) {
|
|
1970
|
+
threadContext.setCurrentThreadId(firstRegularThread[0]);
|
|
1971
|
+
} else {
|
|
1972
|
+
const defaultId = "default-session";
|
|
1973
|
+
threadContext.setThreadMetadata(
|
|
1974
|
+
(prev) => new Map(prev).set(defaultId, {
|
|
1975
|
+
title: "New Chat",
|
|
1976
|
+
status: "regular",
|
|
1977
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1978
|
+
control: initThreadControl()
|
|
1979
|
+
})
|
|
1980
|
+
);
|
|
1981
|
+
threadContext.setThreadMessages(defaultId, []);
|
|
1982
|
+
threadContext.setCurrentThreadId(defaultId);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
} catch (error) {
|
|
1986
|
+
console.error("Failed to delete thread:", error);
|
|
1987
|
+
throw error;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// packages/react/src/interface.tsx
|
|
1994
|
+
import { createContext as createContext6, useContext as useContext6 } from "react";
|
|
1995
|
+
var AomiRuntimeContext = createContext6(null);
|
|
1996
|
+
var AomiRuntimeApiProvider = AomiRuntimeContext.Provider;
|
|
1997
|
+
function useAomiRuntime() {
|
|
1998
|
+
const context = useContext6(AomiRuntimeContext);
|
|
1999
|
+
if (!context) {
|
|
2000
|
+
throw new Error(
|
|
2001
|
+
"useAomiRuntime must be used within AomiRuntimeProvider. Wrap your app with <AomiRuntimeProvider>...</AomiRuntimeProvider>"
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
return context;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// packages/react/src/runtime/core.tsx
|
|
2008
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
2009
|
+
function AomiRuntimeCore({
|
|
2010
|
+
children,
|
|
2011
|
+
backendApi
|
|
2012
|
+
}) {
|
|
2013
|
+
const threadContext = useThreadContext();
|
|
2014
|
+
const eventContext = useEventContext();
|
|
2015
|
+
const notificationContext = useNotification();
|
|
2016
|
+
const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
|
|
2017
|
+
const { user, onUserStateChange, getUserState } = useUser();
|
|
2018
|
+
const { getControlState, getCurrentThreadControl } = useControl();
|
|
2019
|
+
const {
|
|
2020
|
+
backendStateRef,
|
|
2021
|
+
polling,
|
|
2022
|
+
messageController,
|
|
2023
|
+
isRunning,
|
|
2024
|
+
setIsRunning,
|
|
2025
|
+
ensureInitialState,
|
|
2026
|
+
backendApiRef
|
|
2027
|
+
} = useRuntimeOrchestrator(backendApi, {
|
|
2028
|
+
onSyncEvents: dispatchSystemEvents,
|
|
2029
|
+
getPublicKey: () => getUserState().address,
|
|
2030
|
+
getUserState,
|
|
2031
|
+
getNamespace: () => {
|
|
2032
|
+
var _a, _b;
|
|
2033
|
+
return (_b = (_a = getCurrentThreadControl().namespace) != null ? _a : getControlState().defaultNamespace) != null ? _b : "default";
|
|
2034
|
+
},
|
|
2035
|
+
getApiKey: () => getControlState().apiKey
|
|
2036
|
+
});
|
|
2037
|
+
useEffect3(() => {
|
|
2038
|
+
const unsubscribe = onUserStateChange(async (newUser) => {
|
|
2039
|
+
const sessionId = threadContext.currentThreadId;
|
|
2040
|
+
const message = JSON.stringify({
|
|
2041
|
+
type: "wallet:state_changed",
|
|
2042
|
+
payload: {
|
|
2043
|
+
address: newUser.address,
|
|
2044
|
+
chainId: newUser.chainId,
|
|
2045
|
+
isConnected: newUser.isConnected,
|
|
2046
|
+
ensName: newUser.ensName
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
await backendApiRef.current.postSystemMessage(sessionId, message);
|
|
2050
|
+
});
|
|
2051
|
+
return unsubscribe;
|
|
2052
|
+
}, [onUserStateChange, backendApiRef, threadContext.currentThreadId]);
|
|
2053
|
+
const threadContextRef = useRef6(threadContext);
|
|
2054
|
+
threadContextRef.current = threadContext;
|
|
2055
|
+
const currentThreadIdRef = useRef6(threadContext.currentThreadId);
|
|
2056
|
+
useEffect3(() => {
|
|
2057
|
+
currentThreadIdRef.current = threadContext.currentThreadId;
|
|
2058
|
+
}, [threadContext.currentThreadId]);
|
|
2059
|
+
useEffect3(() => {
|
|
2060
|
+
void ensureInitialState(threadContext.currentThreadId);
|
|
2061
|
+
}, [ensureInitialState, threadContext.currentThreadId]);
|
|
2062
|
+
useEffect3(() => {
|
|
2063
|
+
const threadId = threadContext.currentThreadId;
|
|
2064
|
+
setIsRunning(isThreadRunning(backendStateRef.current, threadId));
|
|
2065
|
+
}, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
|
|
2066
|
+
useEffect3(() => {
|
|
2067
|
+
const threadId = threadContext.currentThreadId;
|
|
2068
|
+
const currentMeta = threadContext.getThreadMetadata(threadId);
|
|
2069
|
+
if (currentMeta && currentMeta.control.isProcessing !== isRunning) {
|
|
2070
|
+
threadContext.updateThreadMetadata(threadId, {
|
|
2071
|
+
control: __spreadProps(__spreadValues({}, currentMeta.control), {
|
|
2072
|
+
isProcessing: isRunning
|
|
2073
|
+
})
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}, [isRunning, threadContext]);
|
|
2077
|
+
const currentMessages = threadContext.getThreadMessages(
|
|
2078
|
+
threadContext.currentThreadId
|
|
2079
|
+
);
|
|
2080
|
+
const resolvedSessionId = useMemo2(
|
|
2081
|
+
() => resolveThreadId(backendStateRef.current, threadContext.currentThreadId),
|
|
2082
|
+
[
|
|
2083
|
+
backendStateRef,
|
|
2084
|
+
threadContext.currentThreadId,
|
|
2085
|
+
threadContext.allThreadsMetadata
|
|
2086
|
+
]
|
|
2087
|
+
);
|
|
2088
|
+
useEffect3(() => {
|
|
2089
|
+
const userAddress = user.address;
|
|
2090
|
+
if (!userAddress) return;
|
|
2091
|
+
const fetchThreadList = async () => {
|
|
2092
|
+
var _a, _b, _c;
|
|
2093
|
+
try {
|
|
2094
|
+
const threadList = await backendApiRef.current.fetchThreads(userAddress);
|
|
2095
|
+
const currentContext = threadContextRef.current;
|
|
2096
|
+
const newMetadata = new Map(currentContext.allThreadsMetadata);
|
|
2097
|
+
let maxChatNum = currentContext.threadCnt;
|
|
2098
|
+
for (const thread of threadList) {
|
|
2099
|
+
const rawTitle = (_a = thread.title) != null ? _a : "";
|
|
2100
|
+
const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
|
|
2101
|
+
const lastActive = ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
|
|
2102
|
+
const existingControl = (_c = newMetadata.get(thread.session_id)) == null ? void 0 : _c.control;
|
|
2103
|
+
newMetadata.set(thread.session_id, {
|
|
2104
|
+
title,
|
|
2105
|
+
status: thread.is_archived ? "archived" : "regular",
|
|
2106
|
+
lastActiveAt: lastActive,
|
|
2107
|
+
control: existingControl != null ? existingControl : initThreadControl()
|
|
2108
|
+
});
|
|
2109
|
+
const match = title.match(/^Chat (\d+)$/);
|
|
2110
|
+
if (match) {
|
|
2111
|
+
const num = parseInt(match[1], 10);
|
|
2112
|
+
if (num > maxChatNum) {
|
|
2113
|
+
maxChatNum = num;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
currentContext.setThreadMetadata(newMetadata);
|
|
2118
|
+
if (maxChatNum > currentContext.threadCnt) {
|
|
2119
|
+
currentContext.setThreadCnt(maxChatNum);
|
|
2120
|
+
}
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
console.error("Failed to fetch thread list:", error);
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
void fetchThreadList();
|
|
2126
|
+
}, [user.address, backendApiRef]);
|
|
2127
|
+
const threadListAdapter = useMemo2(
|
|
2128
|
+
() => buildThreadListAdapter({
|
|
2129
|
+
backendStateRef,
|
|
2130
|
+
backendApiRef,
|
|
2131
|
+
threadContext,
|
|
2132
|
+
currentThreadIdRef,
|
|
2133
|
+
polling,
|
|
2134
|
+
userAddress: user.address,
|
|
2135
|
+
setIsRunning,
|
|
2136
|
+
getNamespace: () => {
|
|
2137
|
+
var _a, _b;
|
|
2138
|
+
return (_b = (_a = getCurrentThreadControl().namespace) != null ? _a : getControlState().defaultNamespace) != null ? _b : "default";
|
|
2139
|
+
},
|
|
2140
|
+
getApiKey: () => getControlState().apiKey
|
|
2141
|
+
}),
|
|
2142
|
+
[
|
|
2143
|
+
backendApiRef,
|
|
2144
|
+
polling,
|
|
2145
|
+
user.address,
|
|
2146
|
+
backendStateRef,
|
|
2147
|
+
setIsRunning,
|
|
2148
|
+
threadContext,
|
|
2149
|
+
threadContext.currentThreadId,
|
|
2150
|
+
threadContext.allThreadsMetadata,
|
|
2151
|
+
getControlState
|
|
2152
|
+
]
|
|
2153
|
+
);
|
|
2154
|
+
useEffect3(() => {
|
|
2155
|
+
const backendState = backendStateRef.current;
|
|
2156
|
+
const currentSessionId = threadContext.currentThreadId;
|
|
2157
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2158
|
+
console.debug("[aomi][sse] subscribe", {
|
|
2159
|
+
currentSessionId,
|
|
2160
|
+
resolvedSessionId,
|
|
2161
|
+
hasMapping: currentSessionId !== resolvedSessionId
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
const unsubscribe = backendApiRef.current.subscribeSSE(
|
|
2165
|
+
resolvedSessionId,
|
|
2166
|
+
(event) => {
|
|
2167
|
+
const eventType = event.type;
|
|
2168
|
+
const sessionId = event.session_id;
|
|
2169
|
+
if (eventType === "title_changed") {
|
|
2170
|
+
const newTitle = event.new_title;
|
|
2171
|
+
const targetThreadId = resolveThreadId(backendState, sessionId);
|
|
2172
|
+
const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
|
|
2173
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2174
|
+
console.debug("[aomi][sse] title_changed", {
|
|
2175
|
+
sessionId,
|
|
2176
|
+
newTitle,
|
|
2177
|
+
normalizedTitle,
|
|
2178
|
+
currentThreadId: threadContextRef.current.currentThreadId,
|
|
2179
|
+
targetThreadId,
|
|
2180
|
+
hasMapping: sessionId !== targetThreadId,
|
|
2181
|
+
creatingThreadId: backendState.creatingThreadId
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
threadContextRef.current.setThreadMetadata((prev) => {
|
|
2185
|
+
var _a, _b;
|
|
2186
|
+
const next = new Map(prev);
|
|
2187
|
+
const existing = next.get(targetThreadId);
|
|
2188
|
+
const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
|
|
2189
|
+
next.set(targetThreadId, {
|
|
2190
|
+
title: normalizedTitle,
|
|
2191
|
+
status: nextStatus,
|
|
2192
|
+
lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString(),
|
|
2193
|
+
control: (_b = existing == null ? void 0 : existing.control) != null ? _b : initThreadControl()
|
|
2194
|
+
});
|
|
2195
|
+
return next;
|
|
2196
|
+
});
|
|
2197
|
+
if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === targetThreadId) {
|
|
2198
|
+
backendState.creatingThreadId = null;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
return () => {
|
|
2204
|
+
unsubscribe == null ? void 0 : unsubscribe();
|
|
2205
|
+
};
|
|
2206
|
+
}, [
|
|
2207
|
+
backendApiRef,
|
|
2208
|
+
backendStateRef,
|
|
2209
|
+
threadContext.currentThreadId,
|
|
2210
|
+
resolvedSessionId
|
|
2211
|
+
]);
|
|
2212
|
+
useEffect3(() => {
|
|
2213
|
+
const threadId = threadContext.currentThreadId;
|
|
2214
|
+
if (!isThreadReady(backendStateRef.current, threadId)) return;
|
|
2215
|
+
void messageController.flushPendingChat(threadId);
|
|
2216
|
+
}, [messageController, backendStateRef, threadContext.currentThreadId]);
|
|
2217
|
+
useEffect3(() => {
|
|
2218
|
+
const showToolNotification = (eventType) => (event) => {
|
|
2219
|
+
const payload = event.payload;
|
|
2220
|
+
const toolName = typeof (payload == null ? void 0 : payload.tool_name) === "string" ? payload.tool_name : void 0;
|
|
2221
|
+
const title = toolName ? `${eventType === "tool_update" ? "Tool update" : "Tool complete"}: ${toolName}` : eventType === "tool_update" ? "Tool update" : "Tool complete";
|
|
2222
|
+
const message = typeof (payload == null ? void 0 : payload.message) === "string" ? payload.message : typeof (payload == null ? void 0 : payload.result) === "string" ? payload.result : void 0;
|
|
2223
|
+
notificationContext.showNotification({
|
|
2224
|
+
type: "notice",
|
|
2225
|
+
title,
|
|
2226
|
+
message
|
|
2227
|
+
});
|
|
2228
|
+
};
|
|
2229
|
+
const unsubscribeUpdate = eventContext.subscribe(
|
|
2230
|
+
"tool_update",
|
|
2231
|
+
showToolNotification("tool_update")
|
|
2232
|
+
);
|
|
2233
|
+
const unsubscribeComplete = eventContext.subscribe(
|
|
2234
|
+
"tool_complete",
|
|
2235
|
+
showToolNotification("tool_complete")
|
|
2236
|
+
);
|
|
2237
|
+
return () => {
|
|
2238
|
+
unsubscribeUpdate();
|
|
2239
|
+
unsubscribeComplete();
|
|
2240
|
+
};
|
|
2241
|
+
}, [eventContext, notificationContext]);
|
|
2242
|
+
useEffect3(() => {
|
|
2243
|
+
const unsubscribe = eventContext.subscribe("system_notice", (event) => {
|
|
2244
|
+
const payload = event.payload;
|
|
2245
|
+
const message = payload == null ? void 0 : payload.message;
|
|
2246
|
+
});
|
|
2247
|
+
return unsubscribe;
|
|
2248
|
+
}, [eventContext, notificationContext]);
|
|
2249
|
+
const runtime = useExternalStoreRuntime({
|
|
2250
|
+
messages: currentMessages,
|
|
2251
|
+
setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
|
|
2252
|
+
isRunning,
|
|
2253
|
+
onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
|
|
2254
|
+
onCancel: () => messageController.cancel(threadContext.currentThreadId),
|
|
2255
|
+
convertMessage: (msg) => msg,
|
|
2256
|
+
adapters: { threadList: threadListAdapter }
|
|
2257
|
+
});
|
|
2258
|
+
useEffect3(() => {
|
|
2259
|
+
return () => {
|
|
2260
|
+
polling.stopAll();
|
|
2261
|
+
};
|
|
2262
|
+
}, [polling]);
|
|
2263
|
+
const userContext = useUser();
|
|
2264
|
+
const sendMessage = useCallback6(
|
|
2265
|
+
async (text) => {
|
|
2266
|
+
const appendMessage = {
|
|
2267
|
+
role: "user",
|
|
2268
|
+
content: [{ type: "text", text }]
|
|
2269
|
+
};
|
|
2270
|
+
await messageController.outbound(
|
|
2271
|
+
appendMessage,
|
|
2272
|
+
threadContext.currentThreadId
|
|
2273
|
+
);
|
|
2274
|
+
},
|
|
2275
|
+
[messageController, threadContext.currentThreadId]
|
|
2276
|
+
);
|
|
2277
|
+
const cancelGeneration = useCallback6(() => {
|
|
2278
|
+
messageController.cancel(threadContext.currentThreadId);
|
|
2279
|
+
}, [messageController, threadContext.currentThreadId]);
|
|
2280
|
+
const getMessages = useCallback6(
|
|
2281
|
+
(threadId) => {
|
|
2282
|
+
const id = threadId != null ? threadId : threadContext.currentThreadId;
|
|
2283
|
+
return threadContext.getThreadMessages(id);
|
|
2284
|
+
},
|
|
2285
|
+
[threadContext]
|
|
2286
|
+
);
|
|
2287
|
+
const createThread = useCallback6(async () => {
|
|
2288
|
+
await threadListAdapter.onSwitchToNewThread();
|
|
2289
|
+
return threadContextRef.current.currentThreadId;
|
|
2290
|
+
}, [threadListAdapter]);
|
|
2291
|
+
const deleteThread = useCallback6(
|
|
2292
|
+
async (threadId) => {
|
|
2293
|
+
await threadListAdapter.onDelete(threadId);
|
|
2294
|
+
},
|
|
2295
|
+
[threadListAdapter]
|
|
2296
|
+
);
|
|
2297
|
+
const renameThread = useCallback6(
|
|
2298
|
+
async (threadId, title) => {
|
|
2299
|
+
await threadListAdapter.onRename(threadId, title);
|
|
2300
|
+
},
|
|
2301
|
+
[threadListAdapter]
|
|
2302
|
+
);
|
|
2303
|
+
const archiveThread = useCallback6(
|
|
2304
|
+
async (threadId) => {
|
|
2305
|
+
await threadListAdapter.onArchive(threadId);
|
|
2306
|
+
},
|
|
2307
|
+
[threadListAdapter]
|
|
2308
|
+
);
|
|
2309
|
+
const selectThread = useCallback6(
|
|
2310
|
+
(threadId) => {
|
|
2311
|
+
if (threadContext.allThreadsMetadata.has(threadId)) {
|
|
2312
|
+
threadListAdapter.onSwitchToThread(threadId);
|
|
2313
|
+
} else {
|
|
2314
|
+
void threadListAdapter.onSwitchToNewThread();
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
[threadContext.allThreadsMetadata, threadListAdapter]
|
|
2318
|
+
);
|
|
2319
|
+
const aomiRuntimeApi = useMemo2(
|
|
2320
|
+
() => ({
|
|
2321
|
+
// User API
|
|
2322
|
+
user: userContext.user,
|
|
2323
|
+
getUserState: userContext.getUserState,
|
|
2324
|
+
setUser: userContext.setUser,
|
|
2325
|
+
onUserStateChange: userContext.onUserStateChange,
|
|
2326
|
+
// Thread API
|
|
2327
|
+
currentThreadId: threadContext.currentThreadId,
|
|
2328
|
+
threadViewKey: threadContext.threadViewKey,
|
|
2329
|
+
threadMetadata: threadContext.allThreadsMetadata,
|
|
2330
|
+
getThreadMetadata: threadContext.getThreadMetadata,
|
|
2331
|
+
createThread,
|
|
2332
|
+
deleteThread,
|
|
2333
|
+
renameThread,
|
|
2334
|
+
archiveThread,
|
|
2335
|
+
selectThread,
|
|
2336
|
+
// Chat API
|
|
2337
|
+
isRunning,
|
|
2338
|
+
getMessages,
|
|
2339
|
+
sendMessage,
|
|
2340
|
+
cancelGeneration,
|
|
2341
|
+
// Notification API
|
|
2342
|
+
notifications: notificationContext.notifications,
|
|
2343
|
+
showNotification: notificationContext.showNotification,
|
|
2344
|
+
dismissNotification: notificationContext.dismissNotification,
|
|
2345
|
+
clearAllNotifications: notificationContext.clearAll,
|
|
2346
|
+
// Event API
|
|
2347
|
+
subscribe: eventContext.subscribe,
|
|
2348
|
+
sendSystemCommand: eventContext.sendOutboundSystem,
|
|
2349
|
+
sseStatus: eventContext.sseStatus
|
|
2350
|
+
}),
|
|
2351
|
+
[
|
|
2352
|
+
userContext,
|
|
2353
|
+
threadContext.currentThreadId,
|
|
2354
|
+
threadContext.threadViewKey,
|
|
2355
|
+
threadContext.allThreadsMetadata,
|
|
2356
|
+
threadContext.getThreadMetadata,
|
|
2357
|
+
createThread,
|
|
2358
|
+
deleteThread,
|
|
2359
|
+
renameThread,
|
|
2360
|
+
archiveThread,
|
|
2361
|
+
selectThread,
|
|
2362
|
+
isRunning,
|
|
2363
|
+
getMessages,
|
|
2364
|
+
sendMessage,
|
|
2365
|
+
cancelGeneration,
|
|
2366
|
+
notificationContext,
|
|
2367
|
+
eventContext
|
|
2368
|
+
]
|
|
2369
|
+
);
|
|
2370
|
+
return /* @__PURE__ */ jsx6(AomiRuntimeApiProvider, { value: aomiRuntimeApi, children: /* @__PURE__ */ jsx6(AssistantRuntimeProvider, { runtime, children }) });
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// packages/react/src/runtime/aomi-runtime.tsx
|
|
2374
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2375
|
+
function AomiRuntimeProvider({
|
|
2376
|
+
children,
|
|
2377
|
+
backendUrl = "http://localhost:8080"
|
|
2378
|
+
}) {
|
|
2379
|
+
const backendApi = useMemo3(() => new BackendApi(backendUrl), [backendUrl]);
|
|
2380
|
+
return /* @__PURE__ */ jsx7(ThreadContextProvider, { children: /* @__PURE__ */ jsx7(NotificationContextProvider, { children: /* @__PURE__ */ jsx7(UserContextProvider, { children: /* @__PURE__ */ jsx7(AomiRuntimeInner, { backendApi, children }) }) }) });
|
|
2381
|
+
}
|
|
2382
|
+
function AomiRuntimeInner({
|
|
2383
|
+
children,
|
|
2384
|
+
backendApi
|
|
2385
|
+
}) {
|
|
2386
|
+
var _a;
|
|
2387
|
+
const threadContext = useThreadContext();
|
|
2388
|
+
const { user } = useUser();
|
|
2389
|
+
return /* @__PURE__ */ jsx7(
|
|
2390
|
+
ControlContextProvider,
|
|
2391
|
+
{
|
|
2392
|
+
backendApi,
|
|
2393
|
+
sessionId: threadContext.currentThreadId,
|
|
2394
|
+
publicKey: (_a = user.address) != null ? _a : void 0,
|
|
2395
|
+
getThreadMetadata: threadContext.getThreadMetadata,
|
|
2396
|
+
updateThreadMetadata: threadContext.updateThreadMetadata,
|
|
2397
|
+
children: /* @__PURE__ */ jsx7(
|
|
2398
|
+
EventContextProvider,
|
|
2399
|
+
{
|
|
2400
|
+
backendApi,
|
|
2401
|
+
sessionId: threadContext.currentThreadId,
|
|
2402
|
+
children: /* @__PURE__ */ jsx7(AomiRuntimeCore, { backendApi, children })
|
|
2403
|
+
}
|
|
2404
|
+
)
|
|
2405
|
+
}
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// packages/react/src/handlers/wallet-handler.ts
|
|
2410
|
+
import { useCallback as useCallback7, useEffect as useEffect4, useState as useState6 } from "react";
|
|
2411
|
+
function useWalletHandler({
|
|
2412
|
+
sessionId,
|
|
2413
|
+
onTxRequest
|
|
2414
|
+
}) {
|
|
2415
|
+
const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
|
|
2416
|
+
const { setUser, getUserState } = useUser();
|
|
2417
|
+
const [pendingTxRequests, setPendingTxRequests] = useState6(
|
|
2418
|
+
[]
|
|
2419
|
+
);
|
|
2420
|
+
useEffect4(() => {
|
|
2421
|
+
const unsubscribe = subscribe2(
|
|
2422
|
+
"wallet_tx_request",
|
|
2423
|
+
(event) => {
|
|
2424
|
+
const request = event.payload;
|
|
2425
|
+
setPendingTxRequests((prev) => [...prev, request]);
|
|
2426
|
+
onTxRequest == null ? void 0 : onTxRequest(request);
|
|
2427
|
+
}
|
|
2428
|
+
);
|
|
2429
|
+
return unsubscribe;
|
|
2430
|
+
}, [subscribe2, onTxRequest]);
|
|
2431
|
+
useEffect4(() => {
|
|
2432
|
+
const unsubscribe = subscribe2(
|
|
2433
|
+
"user_state_request",
|
|
2434
|
+
(event) => {
|
|
2435
|
+
sendOutbound({
|
|
2436
|
+
type: "user_state_response",
|
|
2437
|
+
sessionId,
|
|
2438
|
+
payload: getUserState()
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
);
|
|
2442
|
+
return unsubscribe;
|
|
2443
|
+
}, [subscribe2, onTxRequest]);
|
|
2444
|
+
const sendTxComplete = useCallback7(
|
|
2445
|
+
(tx) => {
|
|
2446
|
+
sendOutbound({
|
|
2447
|
+
type: "wallet:tx_complete",
|
|
2448
|
+
sessionId,
|
|
2449
|
+
payload: tx
|
|
2450
|
+
});
|
|
2451
|
+
},
|
|
2452
|
+
[sendOutbound, sessionId]
|
|
2453
|
+
);
|
|
2454
|
+
const sendConnectionChange = useCallback7(
|
|
2455
|
+
(status, address, chainId) => {
|
|
2456
|
+
if (status === "connected") {
|
|
2457
|
+
setUser({
|
|
2458
|
+
isConnected: true,
|
|
2459
|
+
address,
|
|
2460
|
+
chainId
|
|
2461
|
+
});
|
|
2462
|
+
} else {
|
|
2463
|
+
setUser({
|
|
2464
|
+
isConnected: false,
|
|
2465
|
+
address: void 0,
|
|
2466
|
+
chainId: void 0
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
sendOutbound({
|
|
2470
|
+
type: status === "connected" ? "wallet:connected" : "wallet:disconnected",
|
|
2471
|
+
sessionId,
|
|
2472
|
+
payload: { status, address }
|
|
2473
|
+
});
|
|
2474
|
+
},
|
|
2475
|
+
[setUser, sendOutbound, sessionId]
|
|
2476
|
+
);
|
|
2477
|
+
const clearTxRequest = useCallback7((index) => {
|
|
2478
|
+
setPendingTxRequests((prev) => prev.filter((_, i) => i !== index));
|
|
2479
|
+
}, []);
|
|
2480
|
+
return {
|
|
2481
|
+
sendTxComplete,
|
|
2482
|
+
sendConnectionChange,
|
|
2483
|
+
pendingTxRequests,
|
|
2484
|
+
clearTxRequest
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// packages/react/src/handlers/notification-handler.ts
|
|
2489
|
+
import { useCallback as useCallback8, useEffect as useEffect5, useState as useState7 } from "react";
|
|
2490
|
+
var notificationIdCounter2 = 0;
|
|
2491
|
+
function generateNotificationId() {
|
|
2492
|
+
return `notif-${Date.now()}-${++notificationIdCounter2}`;
|
|
2493
|
+
}
|
|
2494
|
+
function useNotificationHandler({
|
|
2495
|
+
onNotification
|
|
2496
|
+
} = {}) {
|
|
2497
|
+
const { subscribe: subscribe2 } = useEventContext();
|
|
2498
|
+
const [notifications, setNotifications] = useState7([]);
|
|
2499
|
+
useEffect5(() => {
|
|
2500
|
+
const unsubscribe = subscribe2("notification", (event) => {
|
|
2501
|
+
var _a, _b;
|
|
2502
|
+
const payload = event.payload;
|
|
2503
|
+
const notification = {
|
|
2504
|
+
id: generateNotificationId(),
|
|
2505
|
+
type: (_a = payload.type) != null ? _a : "notification",
|
|
2506
|
+
title: (_b = payload.title) != null ? _b : "Notification",
|
|
2507
|
+
body: payload.body,
|
|
2508
|
+
handled: false,
|
|
2509
|
+
timestamp: event.timestamp,
|
|
2510
|
+
sessionId: event.sessionId
|
|
2511
|
+
};
|
|
2512
|
+
setNotifications((prev) => [notification, ...prev]);
|
|
2513
|
+
onNotification == null ? void 0 : onNotification(notification);
|
|
2514
|
+
});
|
|
2515
|
+
return unsubscribe;
|
|
2516
|
+
}, [subscribe2, onNotification]);
|
|
2517
|
+
const unhandledCount = notifications.filter((n) => !n.handled).length;
|
|
2518
|
+
const markHandled = useCallback8((id) => {
|
|
2519
|
+
setNotifications(
|
|
2520
|
+
(prev) => prev.map((n) => n.id === id ? __spreadProps(__spreadValues({}, n), { handled: true }) : n)
|
|
2521
|
+
);
|
|
2522
|
+
}, []);
|
|
2523
|
+
return {
|
|
2524
|
+
notifications,
|
|
2525
|
+
unhandledCount,
|
|
2526
|
+
markDone: markHandled
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
export {
|
|
2530
|
+
AomiRuntimeProvider,
|
|
2531
|
+
BackendApi,
|
|
2532
|
+
ControlContextProvider,
|
|
2533
|
+
EventContextProvider,
|
|
2534
|
+
NotificationContextProvider,
|
|
2535
|
+
ThreadContextProvider,
|
|
2536
|
+
UserContextProvider,
|
|
2537
|
+
cn,
|
|
2538
|
+
formatAddress,
|
|
2539
|
+
getNetworkName,
|
|
2540
|
+
initThreadControl,
|
|
2541
|
+
useAomiRuntime,
|
|
2542
|
+
useControl,
|
|
2543
|
+
useCurrentThreadMessages,
|
|
2544
|
+
useCurrentThreadMetadata,
|
|
2545
|
+
useEventContext,
|
|
2546
|
+
useNotification,
|
|
2547
|
+
useNotificationHandler,
|
|
2548
|
+
useThreadContext,
|
|
2549
|
+
useUser,
|
|
2550
|
+
useWalletHandler
|
|
2551
|
+
};
|
|
2552
|
+
//# sourceMappingURL=index.js.map
|