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