@personaai/react 0.8.1 → 0.10.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/README.md +37 -10
- package/dist/index.cjs +1279 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -3
- package/dist/index.d.ts +299 -3
- package/dist/index.js +1277 -129
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -133,7 +133,8 @@ function fetchReader(response, controller) {
|
|
|
133
133
|
function xhrStream(opts) {
|
|
134
134
|
return new Promise((resolveStream, rejectStream) => {
|
|
135
135
|
const xhr = new XMLHttpRequest();
|
|
136
|
-
|
|
136
|
+
const method = opts.method ?? "POST";
|
|
137
|
+
xhr.open(method, opts.url);
|
|
137
138
|
for (const [key, val] of Object.entries(opts.headers))
|
|
138
139
|
xhr.setRequestHeader(key, val);
|
|
139
140
|
let consumed = 0;
|
|
@@ -232,7 +233,7 @@ function xhrStream(opts) {
|
|
|
232
233
|
});
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
|
-
xhr.send(opts.body);
|
|
236
|
+
xhr.send(method === "GET" ? null : opts.body ?? null);
|
|
236
237
|
});
|
|
237
238
|
}
|
|
238
239
|
async function openSSEStream(opts) {
|
|
@@ -244,10 +245,11 @@ async function openSSEStream(opts) {
|
|
|
244
245
|
if (opts.signal.aborted) controller.abort();
|
|
245
246
|
else opts.signal.addEventListener("abort", () => controller.abort());
|
|
246
247
|
}
|
|
248
|
+
const method = opts.method ?? "POST";
|
|
247
249
|
const response = await fetch(opts.url, {
|
|
248
|
-
method
|
|
250
|
+
method,
|
|
249
251
|
headers: opts.headers,
|
|
250
|
-
body: opts.body,
|
|
252
|
+
body: method === "GET" ? void 0 : opts.body,
|
|
251
253
|
signal: controller.signal
|
|
252
254
|
});
|
|
253
255
|
if (!response.ok) {
|
|
@@ -284,7 +286,7 @@ async function openSSEStream(opts) {
|
|
|
284
286
|
};
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
// src/hooks/
|
|
289
|
+
// src/hooks/streamEventHelpers.ts
|
|
288
290
|
function isErrorToolContent(content) {
|
|
289
291
|
if (typeof content !== "string" || !content.trim().startsWith("{"))
|
|
290
292
|
return false;
|
|
@@ -330,6 +332,38 @@ function parsePresentedFile(content) {
|
|
|
330
332
|
return null;
|
|
331
333
|
}
|
|
332
334
|
}
|
|
335
|
+
function normalizeWorkspaceFiles(raw) {
|
|
336
|
+
const normalized = {};
|
|
337
|
+
for (const [path, file] of Object.entries(raw || {})) {
|
|
338
|
+
normalized[path] = {
|
|
339
|
+
content: file.content,
|
|
340
|
+
size: file.size,
|
|
341
|
+
createdAt: file.created_at,
|
|
342
|
+
modifiedAt: file.modified_at
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return normalized;
|
|
346
|
+
}
|
|
347
|
+
function normalizePendingInterrupt(pending) {
|
|
348
|
+
if (!pending || typeof pending !== "object") return null;
|
|
349
|
+
const p = pending;
|
|
350
|
+
if (p.kind === "hitl") {
|
|
351
|
+
return {
|
|
352
|
+
kind: "hitl",
|
|
353
|
+
actionRequests: p.value?.actionRequests ?? [],
|
|
354
|
+
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (p.kind === "clarification") {
|
|
358
|
+
return {
|
|
359
|
+
kind: "clarification",
|
|
360
|
+
questions: p.value?.questions ?? []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/hooks/useChat.ts
|
|
333
367
|
function parseSandboxCommand(tc) {
|
|
334
368
|
let command = tc.args ?? "";
|
|
335
369
|
try {
|
|
@@ -361,36 +395,6 @@ function parseSandboxCommand(tc) {
|
|
|
361
395
|
seq: tc.seq
|
|
362
396
|
};
|
|
363
397
|
}
|
|
364
|
-
function normalizeWorkspaceFiles(raw) {
|
|
365
|
-
const normalized = {};
|
|
366
|
-
for (const [path, file] of Object.entries(raw || {})) {
|
|
367
|
-
normalized[path] = {
|
|
368
|
-
content: file.content,
|
|
369
|
-
size: file.size,
|
|
370
|
-
createdAt: file.created_at,
|
|
371
|
-
modifiedAt: file.modified_at
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
return normalized;
|
|
375
|
-
}
|
|
376
|
-
function normalizePendingInterrupt(pending) {
|
|
377
|
-
if (!pending || typeof pending !== "object") return null;
|
|
378
|
-
const p = pending;
|
|
379
|
-
if (p.kind === "hitl") {
|
|
380
|
-
return {
|
|
381
|
-
kind: "hitl",
|
|
382
|
-
actionRequests: p.value?.actionRequests ?? [],
|
|
383
|
-
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
if (p.kind === "clarification") {
|
|
387
|
-
return {
|
|
388
|
-
kind: "clarification",
|
|
389
|
-
questions: p.value?.questions ?? []
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
398
|
function useChat(options = {}) {
|
|
395
399
|
const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
396
400
|
const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
|
|
@@ -1022,7 +1026,560 @@ function useChat(options = {}) {
|
|
|
1022
1026
|
},
|
|
1023
1027
|
[sendMessage, chatLogger]
|
|
1024
1028
|
);
|
|
1025
|
-
const reload = useCallback(async () => {
|
|
1029
|
+
const reload = useCallback(async () => {
|
|
1030
|
+
if (messages.length === 0 || isStreaming) {
|
|
1031
|
+
chatLogger.trace("reload skipped", {
|
|
1032
|
+
messageCount: messages.length,
|
|
1033
|
+
isStreaming
|
|
1034
|
+
});
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
let lastUserIndex = -1;
|
|
1038
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1039
|
+
if (messages[i].role === "user") {
|
|
1040
|
+
lastUserIndex = i;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (lastUserIndex === -1) {
|
|
1045
|
+
chatLogger.warn("reload no user message", {});
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
const lastUserMessage = messages[lastUserIndex];
|
|
1049
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1050
|
+
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1051
|
+
chatLogger.debug("reload last user", {
|
|
1052
|
+
preview: lastUserMessage.content.slice(0, 100)
|
|
1053
|
+
});
|
|
1054
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1055
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1056
|
+
const resumeInterrupt = useCallback(
|
|
1057
|
+
(resume, displayContent) => {
|
|
1058
|
+
chatLogger.info("resumeInterrupt", {
|
|
1059
|
+
kind: resume.decisions ? "hitl" : "clarification",
|
|
1060
|
+
preview: displayContent.slice(0, 50)
|
|
1061
|
+
});
|
|
1062
|
+
return sendMessage(displayContent, { resume });
|
|
1063
|
+
},
|
|
1064
|
+
[sendMessage, chatLogger]
|
|
1065
|
+
);
|
|
1066
|
+
const dismissPresentedFile = useCallback(() => {
|
|
1067
|
+
chatLogger.debug("dismissPresentedFile", {});
|
|
1068
|
+
setPresentedFile(null);
|
|
1069
|
+
}, [chatLogger]);
|
|
1070
|
+
const openWorkspaceFile = useCallback(
|
|
1071
|
+
(path) => {
|
|
1072
|
+
chatLogger.debug("openWorkspaceFile", { path });
|
|
1073
|
+
setPresentedFile({
|
|
1074
|
+
path,
|
|
1075
|
+
title: path.split("/").pop() || path,
|
|
1076
|
+
description: ""
|
|
1077
|
+
});
|
|
1078
|
+
},
|
|
1079
|
+
[chatLogger]
|
|
1080
|
+
);
|
|
1081
|
+
return {
|
|
1082
|
+
messages,
|
|
1083
|
+
input,
|
|
1084
|
+
setInput,
|
|
1085
|
+
handleInputChange,
|
|
1086
|
+
handleSubmit,
|
|
1087
|
+
sendMessage,
|
|
1088
|
+
isStreaming,
|
|
1089
|
+
isLoading: isStreaming,
|
|
1090
|
+
isLoadingHistory,
|
|
1091
|
+
error,
|
|
1092
|
+
interrupt,
|
|
1093
|
+
resumeInterrupt,
|
|
1094
|
+
files,
|
|
1095
|
+
todos,
|
|
1096
|
+
presentedFile,
|
|
1097
|
+
dismissPresentedFile,
|
|
1098
|
+
openWorkspaceFile,
|
|
1099
|
+
sandboxCommands,
|
|
1100
|
+
stop,
|
|
1101
|
+
reload,
|
|
1102
|
+
clear,
|
|
1103
|
+
startNewChat,
|
|
1104
|
+
currentThreadId: effectiveThreadId,
|
|
1105
|
+
isEphemeral: !effectiveThreadId,
|
|
1106
|
+
setMessages,
|
|
1107
|
+
loadThreadMessages
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/hooks/useArchitectChat.ts
|
|
1112
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
1113
|
+
var ARCHITECT_AGENT_ID = "000000000000000000000002";
|
|
1114
|
+
function useArchitectChat(options = {}) {
|
|
1115
|
+
const { fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
1116
|
+
const chatLogger = useMemo3(() => logger.child("architect"), [logger]);
|
|
1117
|
+
const threadId = options.threadId;
|
|
1118
|
+
const [internalThreadId, setInternalThreadId] = useState2(void 0);
|
|
1119
|
+
const effectiveThreadId = threadId ?? internalThreadId;
|
|
1120
|
+
useEffect2(() => {
|
|
1121
|
+
if (threadId !== void 0) setInternalThreadId(threadId);
|
|
1122
|
+
}, [threadId]);
|
|
1123
|
+
const didLogInitRef = useRef2(false);
|
|
1124
|
+
if (!didLogInitRef.current) {
|
|
1125
|
+
didLogInitRef.current = true;
|
|
1126
|
+
chatLogger.debug("useArchitectChat init", {
|
|
1127
|
+
threadId,
|
|
1128
|
+
hasInitialMessages: !!options.initialMessages?.length
|
|
1129
|
+
});
|
|
1130
|
+
chatLogger.trace("useArchitectChat options", {
|
|
1131
|
+
threadId,
|
|
1132
|
+
initialMessageCount: options.initialMessages?.length ?? 0
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
const [messages, setMessages] = useState2(
|
|
1136
|
+
options.initialMessages || []
|
|
1137
|
+
);
|
|
1138
|
+
const [input, setInput] = useState2("");
|
|
1139
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
1140
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState2(false);
|
|
1141
|
+
const [error, setError] = useState2(null);
|
|
1142
|
+
const [interrupt, setInterrupt] = useState2(null);
|
|
1143
|
+
const [files, setFiles] = useState2({});
|
|
1144
|
+
const [todos, setTodos] = useState2([]);
|
|
1145
|
+
const [presentedFile, setPresentedFile] = useState2(null);
|
|
1146
|
+
const abortControllerRef = useRef2(null);
|
|
1147
|
+
const loadedThreadIdRef = useRef2(void 0);
|
|
1148
|
+
const stop = useCallback2(() => {
|
|
1149
|
+
if (abortControllerRef.current) {
|
|
1150
|
+
chatLogger.info("stop streaming", {});
|
|
1151
|
+
chatLogger.debug("abort controller", {
|
|
1152
|
+
hasController: !!abortControllerRef.current
|
|
1153
|
+
});
|
|
1154
|
+
abortControllerRef.current.abort();
|
|
1155
|
+
abortControllerRef.current = null;
|
|
1156
|
+
setIsStreaming(false);
|
|
1157
|
+
chatLogger.debug("streaming stopped");
|
|
1158
|
+
} else {
|
|
1159
|
+
chatLogger.trace("stop called \u2014 no active stream");
|
|
1160
|
+
}
|
|
1161
|
+
}, [chatLogger]);
|
|
1162
|
+
const clear = useCallback2(() => {
|
|
1163
|
+
chatLogger.info("clear chat", { messageCount: messages.length });
|
|
1164
|
+
chatLogger.debug("clear", { messageCount: messages.length });
|
|
1165
|
+
stop();
|
|
1166
|
+
setMessages([]);
|
|
1167
|
+
setError(null);
|
|
1168
|
+
setInterrupt(null);
|
|
1169
|
+
setFiles({});
|
|
1170
|
+
setTodos([]);
|
|
1171
|
+
setPresentedFile(null);
|
|
1172
|
+
loadedThreadIdRef.current = void 0;
|
|
1173
|
+
}, [stop, chatLogger, messages.length]);
|
|
1174
|
+
const startNewChat = useCallback2(() => {
|
|
1175
|
+
chatLogger.info("startNewChat \u2014 entering ephemeral mode");
|
|
1176
|
+
stop();
|
|
1177
|
+
setMessages([]);
|
|
1178
|
+
setError(null);
|
|
1179
|
+
setInterrupt(null);
|
|
1180
|
+
setFiles({});
|
|
1181
|
+
setTodos([]);
|
|
1182
|
+
setPresentedFile(null);
|
|
1183
|
+
setInput("");
|
|
1184
|
+
loadedThreadIdRef.current = void 0;
|
|
1185
|
+
setInternalThreadId(void 0);
|
|
1186
|
+
}, [stop, chatLogger]);
|
|
1187
|
+
const loadThreadMessages = useCallback2(
|
|
1188
|
+
async (id) => {
|
|
1189
|
+
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
1190
|
+
chatLogger.trace("loadThreadMessages", { threadId: id });
|
|
1191
|
+
setIsLoadingHistory(true);
|
|
1192
|
+
setError(null);
|
|
1193
|
+
try {
|
|
1194
|
+
const res = await fetchWithAuth(`/threads/${id}/messages`);
|
|
1195
|
+
if (!res.ok)
|
|
1196
|
+
throw new Error(`Failed to load thread history: ${res.statusText}`);
|
|
1197
|
+
const body = await res.json();
|
|
1198
|
+
const data = body?.data ?? body;
|
|
1199
|
+
const raw = data?.messages ?? [];
|
|
1200
|
+
const subagentTraces = data?.subagentTraces ?? {};
|
|
1201
|
+
const loaded = raw.map((m, i) => ({
|
|
1202
|
+
id: m.id || `history-${id}-${i}`,
|
|
1203
|
+
role: m.role,
|
|
1204
|
+
content: m.content,
|
|
1205
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1206
|
+
toolCalls: m.toolCalls?.map((tc) => {
|
|
1207
|
+
const trace = subagentTraces[tc.toolCallId];
|
|
1208
|
+
return Array.isArray(trace) && trace.length > 0 ? {
|
|
1209
|
+
...tc,
|
|
1210
|
+
subagentActivity: persistedTraceToActivityEntries(trace)
|
|
1211
|
+
} : tc;
|
|
1212
|
+
})
|
|
1213
|
+
}));
|
|
1214
|
+
setMessages(loaded);
|
|
1215
|
+
setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
|
|
1216
|
+
setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
|
|
1217
|
+
setTodos(data?.state?.todos ?? []);
|
|
1218
|
+
loadedThreadIdRef.current = id;
|
|
1219
|
+
chatLogger.info("loadThreadMessages succeeded", {
|
|
1220
|
+
threadId: id,
|
|
1221
|
+
messageCount: loaded.length
|
|
1222
|
+
});
|
|
1223
|
+
chatLogger.debug("loadThreadMessages completed", {
|
|
1224
|
+
threadId: id,
|
|
1225
|
+
messageCount: loaded.length
|
|
1226
|
+
});
|
|
1227
|
+
return loaded;
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1230
|
+
chatLogger.warn("loadThreadMessages failed", {
|
|
1231
|
+
threadId: id,
|
|
1232
|
+
error: errorObj.message
|
|
1233
|
+
});
|
|
1234
|
+
chatLogger.error("loadThreadMessages error", {
|
|
1235
|
+
threadId: id,
|
|
1236
|
+
error: errorObj.message
|
|
1237
|
+
});
|
|
1238
|
+
setError(errorObj);
|
|
1239
|
+
if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
|
|
1240
|
+
return [];
|
|
1241
|
+
} finally {
|
|
1242
|
+
setIsLoadingHistory(false);
|
|
1243
|
+
chatLogger.trace("loadThreadMessages end", { threadId: id });
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
[fetchWithAuth, chatLogger]
|
|
1247
|
+
);
|
|
1248
|
+
useEffect2(() => {
|
|
1249
|
+
if (!effectiveThreadId || isStreaming) {
|
|
1250
|
+
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (loadedThreadIdRef.current === effectiveThreadId) {
|
|
1254
|
+
chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
if (messages.length > 0) {
|
|
1258
|
+
chatLogger.trace("auto-load has messages", {
|
|
1259
|
+
threadId: effectiveThreadId,
|
|
1260
|
+
count: messages.length
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
chatLogger.info("auto-load thread history", { threadId: effectiveThreadId });
|
|
1265
|
+
chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
|
|
1266
|
+
void loadThreadMessages(effectiveThreadId);
|
|
1267
|
+
}, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
1268
|
+
const doSend = useCallback2(
|
|
1269
|
+
async (prompt, baseMessages, overrideOptions) => {
|
|
1270
|
+
chatLogger.debug("sendMessage start", {
|
|
1271
|
+
promptPreview: prompt.slice(0, 100),
|
|
1272
|
+
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
1273
|
+
hasResume: !!overrideOptions?.resume,
|
|
1274
|
+
messageCount: baseMessages.length
|
|
1275
|
+
});
|
|
1276
|
+
chatLogger.trace("sendMessage details", {
|
|
1277
|
+
promptPreview: prompt.slice(0, 200),
|
|
1278
|
+
hasResume: !!overrideOptions?.resume
|
|
1279
|
+
});
|
|
1280
|
+
const userMessage = {
|
|
1281
|
+
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
1282
|
+
role: "user",
|
|
1283
|
+
content: prompt,
|
|
1284
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1285
|
+
};
|
|
1286
|
+
const assistantMessageId = `assistant-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1287
|
+
const placeholderAssistant = {
|
|
1288
|
+
id: assistantMessageId,
|
|
1289
|
+
role: "assistant",
|
|
1290
|
+
content: "",
|
|
1291
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1292
|
+
isStreaming: true,
|
|
1293
|
+
toolCalls: []
|
|
1294
|
+
};
|
|
1295
|
+
const nextMessages = [...baseMessages, userMessage];
|
|
1296
|
+
setMessages([...nextMessages, placeholderAssistant]);
|
|
1297
|
+
setInput("");
|
|
1298
|
+
setIsStreaming(true);
|
|
1299
|
+
setError(null);
|
|
1300
|
+
setInterrupt(null);
|
|
1301
|
+
const controller = new AbortController();
|
|
1302
|
+
abortControllerRef.current = controller;
|
|
1303
|
+
const finalizeReasoning = () => {
|
|
1304
|
+
setMessages(
|
|
1305
|
+
(prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
|
|
1306
|
+
);
|
|
1307
|
+
};
|
|
1308
|
+
try {
|
|
1309
|
+
const payloadMessages = nextMessages.filter((m) => m.role !== "reasoning").map((m) => ({
|
|
1310
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
1311
|
+
content: m.content
|
|
1312
|
+
}));
|
|
1313
|
+
let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
|
|
1314
|
+
if (!resolvedThreadId) {
|
|
1315
|
+
chatLogger.info("minting real thread for ephemeral architect chat", {});
|
|
1316
|
+
const createRes = await fetchWithAuth("/threads", {
|
|
1317
|
+
method: "POST",
|
|
1318
|
+
headers: { "Content-Type": "application/json" },
|
|
1319
|
+
body: JSON.stringify({ agentId: ARCHITECT_AGENT_ID })
|
|
1320
|
+
});
|
|
1321
|
+
if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
|
|
1322
|
+
const createBody = await createRes.json();
|
|
1323
|
+
const created = createBody?.data ?? createBody;
|
|
1324
|
+
resolvedThreadId = created._id ?? created.id;
|
|
1325
|
+
if (!resolvedThreadId) throw new Error("Thread creation returned no id");
|
|
1326
|
+
setInternalThreadId(resolvedThreadId);
|
|
1327
|
+
loadedThreadIdRef.current = resolvedThreadId;
|
|
1328
|
+
options.onThreadCreated?.(resolvedThreadId);
|
|
1329
|
+
chatLogger.info("ephemeral architect chat minted", { threadId: resolvedThreadId });
|
|
1330
|
+
}
|
|
1331
|
+
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
1332
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
1333
|
+
chatLogger.debug("opening SSE stream", {
|
|
1334
|
+
hasToken: !!token,
|
|
1335
|
+
hasThreadId: !!resolvedThreadId
|
|
1336
|
+
});
|
|
1337
|
+
const stream = await openSSEStream({
|
|
1338
|
+
url: `${baseUrl}/architect`,
|
|
1339
|
+
headers: {
|
|
1340
|
+
"Content-Type": "application/json",
|
|
1341
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1342
|
+
},
|
|
1343
|
+
body: JSON.stringify({
|
|
1344
|
+
messages: payloadMessages,
|
|
1345
|
+
threadId: resolvedThreadId,
|
|
1346
|
+
resume: overrideOptions?.resume
|
|
1347
|
+
}),
|
|
1348
|
+
signal: controller.signal
|
|
1349
|
+
});
|
|
1350
|
+
if (!stream.ok) {
|
|
1351
|
+
chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
|
|
1352
|
+
chatLogger.error("architect stream failed", { status: stream.status, error: stream.errorText });
|
|
1353
|
+
throw new Error(`Architect error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
1354
|
+
}
|
|
1355
|
+
chatLogger.debug("SSE stream opened", { status: stream.status });
|
|
1356
|
+
chatLogger.info("architect stream started", { threadId: resolvedThreadId });
|
|
1357
|
+
const reader = stream.reader;
|
|
1358
|
+
let buffer = "";
|
|
1359
|
+
let accumulatedText = "";
|
|
1360
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
1361
|
+
let streamSeq = 0;
|
|
1362
|
+
let activeReasoningId = null;
|
|
1363
|
+
const reasoningById = /* @__PURE__ */ new Map();
|
|
1364
|
+
const patchAssistant = (patch) => {
|
|
1365
|
+
setMessages(
|
|
1366
|
+
(prev) => prev.map(
|
|
1367
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
|
|
1368
|
+
)
|
|
1369
|
+
);
|
|
1370
|
+
};
|
|
1371
|
+
const insertReasoningMessage = (id, seq) => {
|
|
1372
|
+
const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
|
|
1373
|
+
setMessages((prev) => {
|
|
1374
|
+
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
1375
|
+
if (idx === -1) return [...prev, msg];
|
|
1376
|
+
const next = [...prev];
|
|
1377
|
+
next.splice(idx, 0, msg);
|
|
1378
|
+
return next;
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
while (true) {
|
|
1382
|
+
const { done, value } = await reader.read();
|
|
1383
|
+
if (done) break;
|
|
1384
|
+
buffer += value ?? "";
|
|
1385
|
+
const lines = buffer.split("\n");
|
|
1386
|
+
buffer = lines.pop() ?? "";
|
|
1387
|
+
for (const line of lines) {
|
|
1388
|
+
const trimmed = line.trim();
|
|
1389
|
+
if (!trimmed || !trimmed.startsWith("data:")) continue;
|
|
1390
|
+
const raw = trimmed.replace(/^data:\s*/, "");
|
|
1391
|
+
if (raw === "[DONE]") break;
|
|
1392
|
+
try {
|
|
1393
|
+
const event = JSON.parse(raw);
|
|
1394
|
+
chatLogger.trace("stream event", { type: event.type, event });
|
|
1395
|
+
options.onEvent?.(event);
|
|
1396
|
+
if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
|
|
1397
|
+
chatLogger.debug("text chunk", { deltaLength: event.delta.length });
|
|
1398
|
+
accumulatedText += event.delta;
|
|
1399
|
+
patchAssistant({ content: accumulatedText, isStreaming: true });
|
|
1400
|
+
} else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
|
|
1401
|
+
chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
|
|
1402
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
1403
|
+
if (existing) {
|
|
1404
|
+
existing.args = (existing.args || "") + (event.delta || "");
|
|
1405
|
+
} else {
|
|
1406
|
+
toolCallsMap.set(event.toolCallId, {
|
|
1407
|
+
toolCallId: event.toolCallId,
|
|
1408
|
+
toolName: event.toolCallName || "",
|
|
1409
|
+
args: event.delta || "",
|
|
1410
|
+
seq: streamSeq++
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
patchAssistant({});
|
|
1414
|
+
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
1415
|
+
chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
|
|
1416
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
1417
|
+
if (existing) {
|
|
1418
|
+
existing.result = event.content;
|
|
1419
|
+
existing.isError = isErrorToolContent(event.content);
|
|
1420
|
+
if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
|
|
1421
|
+
if (existing.toolName === "present_file" && !existing.isError) {
|
|
1422
|
+
const presented = parsePresentedFile(event.content);
|
|
1423
|
+
if (presented) {
|
|
1424
|
+
chatLogger.info("present_file", { path: presented.path });
|
|
1425
|
+
setPresentedFile(presented);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
patchAssistant({});
|
|
1429
|
+
}
|
|
1430
|
+
} else if (event.type === "STATE_SNAPSHOT") {
|
|
1431
|
+
chatLogger.debug("state snapshot", {
|
|
1432
|
+
fileCount: Object.keys(event.snapshot.files ?? {}).length,
|
|
1433
|
+
todoCount: event.snapshot.todos?.length ?? 0
|
|
1434
|
+
});
|
|
1435
|
+
setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
|
|
1436
|
+
setTodos(event.snapshot.todos ?? []);
|
|
1437
|
+
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
1438
|
+
chatLogger.debug("reasoning start", { messageId: event.messageId });
|
|
1439
|
+
activeReasoningId = event.messageId;
|
|
1440
|
+
reasoningById.set(event.messageId, { content: "" });
|
|
1441
|
+
insertReasoningMessage(event.messageId, streamSeq++);
|
|
1442
|
+
} else if (event.type === "REASONING_MESSAGE_CONTENT") {
|
|
1443
|
+
let rid = event.messageId || activeReasoningId || "";
|
|
1444
|
+
if (!rid || !reasoningById.has(rid)) {
|
|
1445
|
+
rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1446
|
+
activeReasoningId = rid;
|
|
1447
|
+
reasoningById.set(rid, { content: "" });
|
|
1448
|
+
insertReasoningMessage(rid, streamSeq++);
|
|
1449
|
+
chatLogger.debug("reasoning lazy start", { messageId: rid });
|
|
1450
|
+
}
|
|
1451
|
+
const entry = reasoningById.get(rid);
|
|
1452
|
+
entry.content += event.delta;
|
|
1453
|
+
chatLogger.trace("reasoning content", { messageId: rid, deltaLength: event.delta?.length ?? 0 });
|
|
1454
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m));
|
|
1455
|
+
} else if (event.type === "REASONING_END") {
|
|
1456
|
+
chatLogger.debug("reasoning end", { messageId: activeReasoningId });
|
|
1457
|
+
const rid = activeReasoningId;
|
|
1458
|
+
activeReasoningId = null;
|
|
1459
|
+
if (rid) {
|
|
1460
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
|
|
1461
|
+
}
|
|
1462
|
+
} else if (event.type === "CUSTOM") {
|
|
1463
|
+
chatLogger.debug("custom event", { name: event.name });
|
|
1464
|
+
if (event.name === "hitl_request") {
|
|
1465
|
+
const value2 = event.value;
|
|
1466
|
+
chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
|
|
1467
|
+
setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
|
|
1468
|
+
} else if (event.name === "clarification_request") {
|
|
1469
|
+
const value2 = event.value;
|
|
1470
|
+
chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
|
|
1471
|
+
setInterrupt({ kind: "clarification", questions: value2.questions });
|
|
1472
|
+
} else if (event.name === "subagent_activity") {
|
|
1473
|
+
const { toolCallId, ...entry } = event.value;
|
|
1474
|
+
chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
|
|
1475
|
+
const existing = toolCallsMap.get(toolCallId);
|
|
1476
|
+
if (existing) {
|
|
1477
|
+
existing.subagentActivity = [...existing.subagentActivity || [], entry];
|
|
1478
|
+
patchAssistant({});
|
|
1479
|
+
}
|
|
1480
|
+
} else if (event.name === "mcp_app") {
|
|
1481
|
+
const val = event.value;
|
|
1482
|
+
if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
|
|
1483
|
+
const existing = toolCallsMap.get(val.toolCallId);
|
|
1484
|
+
if (existing) {
|
|
1485
|
+
existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
|
|
1486
|
+
patchAssistant({});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
} else if (event.type === "title") {
|
|
1491
|
+
const title = event.title;
|
|
1492
|
+
if (typeof title === "string" && title.trim()) {
|
|
1493
|
+
chatLogger.info("auto title", { title });
|
|
1494
|
+
options.onTitle?.(title);
|
|
1495
|
+
}
|
|
1496
|
+
} else if (event.type === "RUN_ERROR") {
|
|
1497
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
1498
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
1499
|
+
throw new Error(event.message || "Stream error from agent");
|
|
1500
|
+
} else {
|
|
1501
|
+
chatLogger.trace("unhandled event", { type: event.type });
|
|
1502
|
+
}
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
|
|
1505
|
+
chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const finalMessage = {
|
|
1510
|
+
id: assistantMessageId,
|
|
1511
|
+
role: "assistant",
|
|
1512
|
+
content: accumulatedText,
|
|
1513
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1514
|
+
isStreaming: false,
|
|
1515
|
+
toolCalls: Array.from(toolCallsMap.values())
|
|
1516
|
+
};
|
|
1517
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
1518
|
+
chatLogger.info("sendMessage succeeded", { textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
1519
|
+
chatLogger.debug("sendMessage completed", { textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
1520
|
+
options.onFinish?.(finalMessage);
|
|
1521
|
+
return true;
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
if (controller.signal.aborted) {
|
|
1524
|
+
chatLogger.info("sendMessage aborted", {});
|
|
1525
|
+
chatLogger.debug("stream aborted", {});
|
|
1526
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1530
|
+
chatLogger.warn("sendMessage failed", { error: errorObj.message });
|
|
1531
|
+
chatLogger.error("sendMessage error", { error: errorObj.message });
|
|
1532
|
+
setError(errorObj);
|
|
1533
|
+
options.onError?.(errorObj);
|
|
1534
|
+
setMessages(
|
|
1535
|
+
(prev) => prev.map(
|
|
1536
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1539
|
+
return false;
|
|
1540
|
+
} finally {
|
|
1541
|
+
setIsStreaming(false);
|
|
1542
|
+
abortControllerRef.current = null;
|
|
1543
|
+
finalizeReasoning();
|
|
1544
|
+
chatLogger.debug("sendMessage finally", { isStreaming: false });
|
|
1545
|
+
chatLogger.trace("sendMessage end", {});
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
[baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
1549
|
+
);
|
|
1550
|
+
const sendMessage = useCallback2(
|
|
1551
|
+
async (contentToSend, overrideOptions) => {
|
|
1552
|
+
const prompt = (contentToSend ?? input).trim();
|
|
1553
|
+
if (!prompt || isStreaming) {
|
|
1554
|
+
chatLogger.trace("sendMessage skipped", {
|
|
1555
|
+
hasPrompt: !!prompt,
|
|
1556
|
+
isStreaming
|
|
1557
|
+
});
|
|
1558
|
+
if (isStreaming) {
|
|
1559
|
+
chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
|
|
1560
|
+
}
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
return doSend(prompt, messages, overrideOptions);
|
|
1564
|
+
},
|
|
1565
|
+
[input, isStreaming, messages, doSend, chatLogger]
|
|
1566
|
+
);
|
|
1567
|
+
const handleInputChange = useCallback2(
|
|
1568
|
+
(e) => {
|
|
1569
|
+
chatLogger.trace("handleInputChange", { length: e.target.value.length });
|
|
1570
|
+
setInput(e.target.value);
|
|
1571
|
+
},
|
|
1572
|
+
[chatLogger]
|
|
1573
|
+
);
|
|
1574
|
+
const handleSubmit = useCallback2(
|
|
1575
|
+
(e) => {
|
|
1576
|
+
if (e) e.preventDefault();
|
|
1577
|
+
chatLogger.debug("handleSubmit", {});
|
|
1578
|
+
void sendMessage();
|
|
1579
|
+
},
|
|
1580
|
+
[sendMessage, chatLogger]
|
|
1581
|
+
);
|
|
1582
|
+
const reload = useCallback2(async () => {
|
|
1026
1583
|
if (messages.length === 0 || isStreaming) {
|
|
1027
1584
|
chatLogger.trace("reload skipped", {
|
|
1028
1585
|
messageCount: messages.length,
|
|
@@ -1049,7 +1606,7 @@ function useChat(options = {}) {
|
|
|
1049
1606
|
});
|
|
1050
1607
|
return doSend(lastUserMessage.content, truncated);
|
|
1051
1608
|
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1052
|
-
const resumeInterrupt =
|
|
1609
|
+
const resumeInterrupt = useCallback2(
|
|
1053
1610
|
(resume, displayContent) => {
|
|
1054
1611
|
chatLogger.info("resumeInterrupt", {
|
|
1055
1612
|
kind: resume.decisions ? "hitl" : "clarification",
|
|
@@ -1059,11 +1616,11 @@ function useChat(options = {}) {
|
|
|
1059
1616
|
},
|
|
1060
1617
|
[sendMessage, chatLogger]
|
|
1061
1618
|
);
|
|
1062
|
-
const dismissPresentedFile =
|
|
1619
|
+
const dismissPresentedFile = useCallback2(() => {
|
|
1063
1620
|
chatLogger.debug("dismissPresentedFile", {});
|
|
1064
1621
|
setPresentedFile(null);
|
|
1065
1622
|
}, [chatLogger]);
|
|
1066
|
-
const openWorkspaceFile =
|
|
1623
|
+
const openWorkspaceFile = useCallback2(
|
|
1067
1624
|
(path) => {
|
|
1068
1625
|
chatLogger.debug("openWorkspaceFile", { path });
|
|
1069
1626
|
setPresentedFile({
|
|
@@ -1092,7 +1649,6 @@ function useChat(options = {}) {
|
|
|
1092
1649
|
presentedFile,
|
|
1093
1650
|
dismissPresentedFile,
|
|
1094
1651
|
openWorkspaceFile,
|
|
1095
|
-
sandboxCommands,
|
|
1096
1652
|
stop,
|
|
1097
1653
|
reload,
|
|
1098
1654
|
clear,
|
|
@@ -1105,16 +1661,16 @@ function useChat(options = {}) {
|
|
|
1105
1661
|
}
|
|
1106
1662
|
|
|
1107
1663
|
// src/hooks/useMemory.ts
|
|
1108
|
-
import { useCallback as
|
|
1664
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
|
|
1109
1665
|
function useMemory(autoFetch = true) {
|
|
1110
1666
|
const { fetchWithAuth } = usePersonaContext();
|
|
1111
|
-
const [memory, setMemory] =
|
|
1667
|
+
const [memory, setMemory] = useState3({
|
|
1112
1668
|
userFiles: [],
|
|
1113
1669
|
agentMemories: []
|
|
1114
1670
|
});
|
|
1115
|
-
const [isLoading, setIsLoading] =
|
|
1116
|
-
const [error, setError] =
|
|
1117
|
-
const fetchMemory =
|
|
1671
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1672
|
+
const [error, setError] = useState3(null);
|
|
1673
|
+
const fetchMemory = useCallback3(async () => {
|
|
1118
1674
|
setIsLoading(true);
|
|
1119
1675
|
setError(null);
|
|
1120
1676
|
try {
|
|
@@ -1133,7 +1689,7 @@ function useMemory(autoFetch = true) {
|
|
|
1133
1689
|
setIsLoading(false);
|
|
1134
1690
|
}
|
|
1135
1691
|
}, [fetchWithAuth]);
|
|
1136
|
-
const getFile =
|
|
1692
|
+
const getFile = useCallback3(
|
|
1137
1693
|
async (params) => {
|
|
1138
1694
|
const query = new URLSearchParams({ path: params.path });
|
|
1139
1695
|
if (params.scope) query.set("scope", params.scope);
|
|
@@ -1145,7 +1701,7 @@ function useMemory(autoFetch = true) {
|
|
|
1145
1701
|
},
|
|
1146
1702
|
[fetchWithAuth]
|
|
1147
1703
|
);
|
|
1148
|
-
const writeFile =
|
|
1704
|
+
const writeFile = useCallback3(
|
|
1149
1705
|
async (params) => {
|
|
1150
1706
|
const res = await fetchWithAuth("/memory/file", {
|
|
1151
1707
|
method: "PUT",
|
|
@@ -1160,7 +1716,7 @@ function useMemory(autoFetch = true) {
|
|
|
1160
1716
|
},
|
|
1161
1717
|
[fetchWithAuth, fetchMemory]
|
|
1162
1718
|
);
|
|
1163
|
-
const deleteFile =
|
|
1719
|
+
const deleteFile = useCallback3(
|
|
1164
1720
|
async (params) => {
|
|
1165
1721
|
const query = new URLSearchParams({ path: params.path });
|
|
1166
1722
|
if (params.scope) query.set("scope", params.scope);
|
|
@@ -1174,7 +1730,7 @@ function useMemory(autoFetch = true) {
|
|
|
1174
1730
|
},
|
|
1175
1731
|
[fetchWithAuth, fetchMemory]
|
|
1176
1732
|
);
|
|
1177
|
-
|
|
1733
|
+
useEffect3(() => {
|
|
1178
1734
|
if (autoFetch) {
|
|
1179
1735
|
void fetchMemory();
|
|
1180
1736
|
}
|
|
@@ -1191,13 +1747,13 @@ function useMemory(autoFetch = true) {
|
|
|
1191
1747
|
}
|
|
1192
1748
|
|
|
1193
1749
|
// src/hooks/useThreads.ts
|
|
1194
|
-
import { useCallback as
|
|
1750
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
|
|
1195
1751
|
function useThreads(autoFetch = true) {
|
|
1196
1752
|
const { fetchWithAuth, defaultAgentId } = usePersonaContext();
|
|
1197
|
-
const [threads, setThreads] =
|
|
1198
|
-
const [isLoading, setIsLoading] =
|
|
1199
|
-
const [error, setError] =
|
|
1200
|
-
const fetchThreads =
|
|
1753
|
+
const [threads, setThreads] = useState4([]);
|
|
1754
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1755
|
+
const [error, setError] = useState4(null);
|
|
1756
|
+
const fetchThreads = useCallback4(async () => {
|
|
1201
1757
|
setIsLoading(true);
|
|
1202
1758
|
setError(null);
|
|
1203
1759
|
try {
|
|
@@ -1215,7 +1771,7 @@ function useThreads(autoFetch = true) {
|
|
|
1215
1771
|
setIsLoading(false);
|
|
1216
1772
|
}
|
|
1217
1773
|
}, [fetchWithAuth]);
|
|
1218
|
-
const createThread =
|
|
1774
|
+
const createThread = useCallback4(
|
|
1219
1775
|
async (agentId) => {
|
|
1220
1776
|
const targetAgentId = agentId || defaultAgentId;
|
|
1221
1777
|
const res = await fetchWithAuth("/threads", {
|
|
@@ -1232,7 +1788,7 @@ function useThreads(autoFetch = true) {
|
|
|
1232
1788
|
},
|
|
1233
1789
|
[fetchWithAuth, defaultAgentId, fetchThreads]
|
|
1234
1790
|
);
|
|
1235
|
-
const deleteThread =
|
|
1791
|
+
const deleteThread = useCallback4(
|
|
1236
1792
|
async (threadId) => {
|
|
1237
1793
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1238
1794
|
method: "DELETE"
|
|
@@ -1243,7 +1799,7 @@ function useThreads(autoFetch = true) {
|
|
|
1243
1799
|
},
|
|
1244
1800
|
[fetchWithAuth]
|
|
1245
1801
|
);
|
|
1246
|
-
const bulkDeleteThreads =
|
|
1802
|
+
const bulkDeleteThreads = useCallback4(
|
|
1247
1803
|
async (threadIds) => {
|
|
1248
1804
|
const res = await fetchWithAuth("/threads/bulk-delete", {
|
|
1249
1805
|
method: "POST",
|
|
@@ -1260,13 +1816,13 @@ function useThreads(autoFetch = true) {
|
|
|
1260
1816
|
},
|
|
1261
1817
|
[fetchWithAuth]
|
|
1262
1818
|
);
|
|
1263
|
-
const deleteAllThreads =
|
|
1819
|
+
const deleteAllThreads = useCallback4(async () => {
|
|
1264
1820
|
const ids = threads.map((t) => t._id);
|
|
1265
1821
|
for (let i = 0; i < ids.length; i += 100) {
|
|
1266
1822
|
await bulkDeleteThreads(ids.slice(i, i + 100));
|
|
1267
1823
|
}
|
|
1268
1824
|
}, [threads, bulkDeleteThreads]);
|
|
1269
|
-
const updateThread =
|
|
1825
|
+
const updateThread = useCallback4(
|
|
1270
1826
|
async (threadId, input) => {
|
|
1271
1827
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1272
1828
|
method: "PATCH",
|
|
@@ -1284,11 +1840,11 @@ function useThreads(autoFetch = true) {
|
|
|
1284
1840
|
},
|
|
1285
1841
|
[fetchWithAuth]
|
|
1286
1842
|
);
|
|
1287
|
-
const renameThread =
|
|
1843
|
+
const renameThread = useCallback4(
|
|
1288
1844
|
(threadId, title) => updateThread(threadId, { title }),
|
|
1289
1845
|
[updateThread]
|
|
1290
1846
|
);
|
|
1291
|
-
const resetThread =
|
|
1847
|
+
const resetThread = useCallback4(
|
|
1292
1848
|
async (threadId) => {
|
|
1293
1849
|
const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
|
|
1294
1850
|
method: "POST"
|
|
@@ -1303,7 +1859,7 @@ function useThreads(autoFetch = true) {
|
|
|
1303
1859
|
},
|
|
1304
1860
|
[fetchWithAuth]
|
|
1305
1861
|
);
|
|
1306
|
-
const getThread =
|
|
1862
|
+
const getThread = useCallback4(
|
|
1307
1863
|
async (threadId) => {
|
|
1308
1864
|
const res = await fetchWithAuth(`/threads/${threadId}`);
|
|
1309
1865
|
if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
|
|
@@ -1312,7 +1868,7 @@ function useThreads(autoFetch = true) {
|
|
|
1312
1868
|
},
|
|
1313
1869
|
[fetchWithAuth]
|
|
1314
1870
|
);
|
|
1315
|
-
|
|
1871
|
+
useEffect4(() => {
|
|
1316
1872
|
if (autoFetch) {
|
|
1317
1873
|
void fetchThreads();
|
|
1318
1874
|
}
|
|
@@ -1334,7 +1890,7 @@ function useThreads(autoFetch = true) {
|
|
|
1334
1890
|
}
|
|
1335
1891
|
|
|
1336
1892
|
// src/hooks/useVoice.ts
|
|
1337
|
-
import { useCallback as
|
|
1893
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
|
|
1338
1894
|
|
|
1339
1895
|
// src/hooks/voiceWorklets.ts
|
|
1340
1896
|
var RECORDER_WORKLET_SOURCE = `
|
|
@@ -1437,42 +1993,42 @@ function mergeTranscriptText(prev, next) {
|
|
|
1437
1993
|
}
|
|
1438
1994
|
function useVoice(options = {}) {
|
|
1439
1995
|
const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
|
|
1440
|
-
const voiceLogger =
|
|
1996
|
+
const voiceLogger = useMemo4(() => logger.child("voice"), [logger]);
|
|
1441
1997
|
const agentId = options.agentId || defaultAgentId;
|
|
1442
1998
|
const threadId = options.threadId;
|
|
1443
1999
|
const contextOverride = options.contextOverride;
|
|
1444
2000
|
const context = options.context;
|
|
1445
|
-
const [state, setState] =
|
|
1446
|
-
const [isMuted, setIsMuted] =
|
|
1447
|
-
const [transcript, setTranscript] =
|
|
2001
|
+
const [state, setState] = useState5("idle");
|
|
2002
|
+
const [isMuted, setIsMuted] = useState5(false);
|
|
2003
|
+
const [transcript, setTranscript] = useState5(
|
|
1448
2004
|
[]
|
|
1449
2005
|
);
|
|
1450
|
-
const [partial, setPartial] =
|
|
2006
|
+
const [partial, setPartial] = useState5(
|
|
1451
2007
|
null
|
|
1452
2008
|
);
|
|
1453
|
-
const [toolCalls, setToolCalls] =
|
|
1454
|
-
const [error, setError] =
|
|
1455
|
-
const [endReason, setEndReason] =
|
|
2009
|
+
const [toolCalls, setToolCalls] = useState5([]);
|
|
2010
|
+
const [error, setError] = useState5(null);
|
|
2011
|
+
const [endReason, setEndReason] = useState5(
|
|
1456
2012
|
null
|
|
1457
2013
|
);
|
|
1458
|
-
const wsRef =
|
|
1459
|
-
const streamRef =
|
|
1460
|
-
const inputCtxRef =
|
|
1461
|
-
const outputCtxRef =
|
|
1462
|
-
const recorderNodeRef =
|
|
1463
|
-
const playerNodeRef =
|
|
1464
|
-
const acceptedTurnSeqRef =
|
|
1465
|
-
const mountedRef =
|
|
1466
|
-
const lastFinalTurnRef =
|
|
2014
|
+
const wsRef = useRef3(null);
|
|
2015
|
+
const streamRef = useRef3(null);
|
|
2016
|
+
const inputCtxRef = useRef3(null);
|
|
2017
|
+
const outputCtxRef = useRef3(null);
|
|
2018
|
+
const recorderNodeRef = useRef3(null);
|
|
2019
|
+
const playerNodeRef = useRef3(null);
|
|
2020
|
+
const acceptedTurnSeqRef = useRef3(0);
|
|
2021
|
+
const mountedRef = useRef3(true);
|
|
2022
|
+
const lastFinalTurnRef = useRef3(
|
|
1467
2023
|
null
|
|
1468
2024
|
);
|
|
1469
|
-
|
|
2025
|
+
useEffect5(() => {
|
|
1470
2026
|
mountedRef.current = true;
|
|
1471
2027
|
return () => {
|
|
1472
2028
|
mountedRef.current = false;
|
|
1473
2029
|
};
|
|
1474
2030
|
}, []);
|
|
1475
|
-
const teardownAudio =
|
|
2031
|
+
const teardownAudio = useCallback5(() => {
|
|
1476
2032
|
try {
|
|
1477
2033
|
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
1478
2034
|
} catch {
|
|
@@ -1499,7 +2055,7 @@ function useVoice(options = {}) {
|
|
|
1499
2055
|
}
|
|
1500
2056
|
outputCtxRef.current = null;
|
|
1501
2057
|
}, []);
|
|
1502
|
-
const stop =
|
|
2058
|
+
const stop = useCallback5(() => {
|
|
1503
2059
|
voiceLogger.debug("stop() called");
|
|
1504
2060
|
try {
|
|
1505
2061
|
wsRef.current?.close(1e3, "client_stop");
|
|
@@ -1509,8 +2065,8 @@ function useVoice(options = {}) {
|
|
|
1509
2065
|
teardownAudio();
|
|
1510
2066
|
if (mountedRef.current) setState("idle");
|
|
1511
2067
|
}, [teardownAudio, voiceLogger]);
|
|
1512
|
-
|
|
1513
|
-
const startCapture =
|
|
2068
|
+
useEffect5(() => stop, [stop]);
|
|
2069
|
+
const startCapture = useCallback5(async (inputSampleRate) => {
|
|
1514
2070
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1515
2071
|
audio: {
|
|
1516
2072
|
echoCancellation: true,
|
|
@@ -1536,7 +2092,7 @@ function useVoice(options = {}) {
|
|
|
1536
2092
|
recorderNodeRef.current = recorderNode;
|
|
1537
2093
|
source.connect(recorderNode);
|
|
1538
2094
|
}, []);
|
|
1539
|
-
const setupPlayback =
|
|
2095
|
+
const setupPlayback = useCallback5(async (outputSampleRate) => {
|
|
1540
2096
|
const outputCtx = new AudioContext({ sampleRate: outputSampleRate });
|
|
1541
2097
|
outputCtxRef.current = outputCtx;
|
|
1542
2098
|
await outputCtx.audioWorklet.addModule(playerWorkletUrl());
|
|
@@ -1544,7 +2100,7 @@ function useVoice(options = {}) {
|
|
|
1544
2100
|
playerNode.connect(outputCtx.destination);
|
|
1545
2101
|
playerNodeRef.current = playerNode;
|
|
1546
2102
|
}, []);
|
|
1547
|
-
const upsertToolCall =
|
|
2103
|
+
const upsertToolCall = useCallback5(
|
|
1548
2104
|
(id, patch) => {
|
|
1549
2105
|
setToolCalls((prev) => {
|
|
1550
2106
|
const idx = prev.findIndex((t) => t.id === id);
|
|
@@ -1556,7 +2112,7 @@ function useVoice(options = {}) {
|
|
|
1556
2112
|
},
|
|
1557
2113
|
[]
|
|
1558
2114
|
);
|
|
1559
|
-
const handleTranscript =
|
|
2115
|
+
const handleTranscript = useCallback5((value) => {
|
|
1560
2116
|
const { speaker, text, isFinal, turnSeq } = value;
|
|
1561
2117
|
if (!isFinal) {
|
|
1562
2118
|
setPartial({ id: "partial", speaker, text });
|
|
@@ -1579,7 +2135,7 @@ function useVoice(options = {}) {
|
|
|
1579
2135
|
});
|
|
1580
2136
|
lastFinalTurnRef.current = { speaker, turnSeq };
|
|
1581
2137
|
}, []);
|
|
1582
|
-
const handleCustomEvent =
|
|
2138
|
+
const handleCustomEvent = useCallback5(
|
|
1583
2139
|
(name, value) => {
|
|
1584
2140
|
switch (name) {
|
|
1585
2141
|
case "voice_session_ready":
|
|
@@ -1637,7 +2193,7 @@ function useVoice(options = {}) {
|
|
|
1637
2193
|
},
|
|
1638
2194
|
[handleTranscript, setupPlayback, startCapture, teardownAudio, voiceLogger]
|
|
1639
2195
|
);
|
|
1640
|
-
const handleMessage =
|
|
2196
|
+
const handleMessage = useCallback5(
|
|
1641
2197
|
(event) => {
|
|
1642
2198
|
if (typeof event.data !== "string") {
|
|
1643
2199
|
const buf = event.data;
|
|
@@ -1695,7 +2251,7 @@ function useVoice(options = {}) {
|
|
|
1695
2251
|
},
|
|
1696
2252
|
[handleCustomEvent, upsertToolCall, voiceLogger]
|
|
1697
2253
|
);
|
|
1698
|
-
const start =
|
|
2254
|
+
const start = useCallback5(async () => {
|
|
1699
2255
|
if (!agentId) {
|
|
1700
2256
|
const err = new Error(
|
|
1701
2257
|
"useVoice: no agentId provided and no defaultAgentId set on PersonaProvider"
|
|
@@ -1762,17 +2318,17 @@ function useVoice(options = {}) {
|
|
|
1762
2318
|
teardownAudio();
|
|
1763
2319
|
}
|
|
1764
2320
|
}, [agentId, threadId, contextOverride, context, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
|
|
1765
|
-
const mute =
|
|
2321
|
+
const mute = useCallback5((muted) => {
|
|
1766
2322
|
setIsMuted(muted);
|
|
1767
2323
|
streamRef.current?.getAudioTracks().forEach((track) => {
|
|
1768
2324
|
track.enabled = !muted;
|
|
1769
2325
|
});
|
|
1770
2326
|
}, []);
|
|
1771
|
-
const sendText =
|
|
2327
|
+
const sendText = useCallback5((text) => {
|
|
1772
2328
|
if (!text?.trim() || wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1773
2329
|
wsRef.current.send(JSON.stringify({ type: "voice.text", text }));
|
|
1774
2330
|
}, []);
|
|
1775
|
-
const updateContext =
|
|
2331
|
+
const updateContext = useCallback5((newContext) => {
|
|
1776
2332
|
if (wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1777
2333
|
wsRef.current.send(JSON.stringify({ type: "voice.context", context: newContext }));
|
|
1778
2334
|
}, []);
|
|
@@ -1793,14 +2349,14 @@ function useVoice(options = {}) {
|
|
|
1793
2349
|
}
|
|
1794
2350
|
|
|
1795
2351
|
// src/hooks/useFiles.ts
|
|
1796
|
-
import { useCallback as
|
|
2352
|
+
import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
|
|
1797
2353
|
function useFiles(autoFetch = true) {
|
|
1798
2354
|
const { fetchWithAuth } = usePersonaContext();
|
|
1799
|
-
const [files, setFiles] =
|
|
1800
|
-
const [isLoading, setIsLoading] =
|
|
1801
|
-
const [isUploading, setIsUploading] =
|
|
1802
|
-
const [error, setError] =
|
|
1803
|
-
const fetchFiles =
|
|
2355
|
+
const [files, setFiles] = useState6([]);
|
|
2356
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
2357
|
+
const [isUploading, setIsUploading] = useState6(false);
|
|
2358
|
+
const [error, setError] = useState6(null);
|
|
2359
|
+
const fetchFiles = useCallback6(async () => {
|
|
1804
2360
|
setIsLoading(true);
|
|
1805
2361
|
setError(null);
|
|
1806
2362
|
try {
|
|
@@ -1818,7 +2374,7 @@ function useFiles(autoFetch = true) {
|
|
|
1818
2374
|
setIsLoading(false);
|
|
1819
2375
|
}
|
|
1820
2376
|
}, [fetchWithAuth]);
|
|
1821
|
-
const uploadFile =
|
|
2377
|
+
const uploadFile = useCallback6(
|
|
1822
2378
|
async (fileOrFormData) => {
|
|
1823
2379
|
setIsUploading(true);
|
|
1824
2380
|
setError(null);
|
|
@@ -1851,7 +2407,7 @@ function useFiles(autoFetch = true) {
|
|
|
1851
2407
|
},
|
|
1852
2408
|
[fetchWithAuth, fetchFiles]
|
|
1853
2409
|
);
|
|
1854
|
-
const deleteFile =
|
|
2410
|
+
const deleteFile = useCallback6(
|
|
1855
2411
|
async (fileId) => {
|
|
1856
2412
|
const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
|
|
1857
2413
|
if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
|
|
@@ -1859,7 +2415,7 @@ function useFiles(autoFetch = true) {
|
|
|
1859
2415
|
},
|
|
1860
2416
|
[fetchWithAuth]
|
|
1861
2417
|
);
|
|
1862
|
-
const bulkDeleteFiles =
|
|
2418
|
+
const bulkDeleteFiles = useCallback6(
|
|
1863
2419
|
async (fileIds) => {
|
|
1864
2420
|
const res = await fetchWithAuth("/files/bulk-delete", {
|
|
1865
2421
|
method: "POST",
|
|
@@ -1874,10 +2430,10 @@ function useFiles(autoFetch = true) {
|
|
|
1874
2430
|
},
|
|
1875
2431
|
[fetchWithAuth]
|
|
1876
2432
|
);
|
|
1877
|
-
const getDownloadUrl =
|
|
2433
|
+
const getDownloadUrl = useCallback6((fileId) => {
|
|
1878
2434
|
return `/files/${fileId}`;
|
|
1879
2435
|
}, []);
|
|
1880
|
-
|
|
2436
|
+
useEffect6(() => {
|
|
1881
2437
|
if (autoFetch) {
|
|
1882
2438
|
void fetchFiles();
|
|
1883
2439
|
}
|
|
@@ -1896,13 +2452,13 @@ function useFiles(autoFetch = true) {
|
|
|
1896
2452
|
}
|
|
1897
2453
|
|
|
1898
2454
|
// src/hooks/useAgents.ts
|
|
1899
|
-
import { useCallback as
|
|
2455
|
+
import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
|
|
1900
2456
|
function useAgents(autoFetch = true) {
|
|
1901
2457
|
const { fetchWithAuth } = usePersonaContext();
|
|
1902
|
-
const [agents, setAgents] =
|
|
1903
|
-
const [isLoading, setIsLoading] =
|
|
1904
|
-
const [error, setError] =
|
|
1905
|
-
const fetchAgents =
|
|
2458
|
+
const [agents, setAgents] = useState7([]);
|
|
2459
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
2460
|
+
const [error, setError] = useState7(null);
|
|
2461
|
+
const fetchAgents = useCallback7(async () => {
|
|
1906
2462
|
setIsLoading(true);
|
|
1907
2463
|
setError(null);
|
|
1908
2464
|
try {
|
|
@@ -1920,7 +2476,7 @@ function useAgents(autoFetch = true) {
|
|
|
1920
2476
|
setIsLoading(false);
|
|
1921
2477
|
}
|
|
1922
2478
|
}, [fetchWithAuth]);
|
|
1923
|
-
|
|
2479
|
+
useEffect7(() => {
|
|
1924
2480
|
if (autoFetch) {
|
|
1925
2481
|
void fetchAgents();
|
|
1926
2482
|
}
|
|
@@ -1934,13 +2490,13 @@ function useAgents(autoFetch = true) {
|
|
|
1934
2490
|
}
|
|
1935
2491
|
|
|
1936
2492
|
// src/hooks/useConnection.ts
|
|
1937
|
-
import { useCallback as
|
|
2493
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
|
|
1938
2494
|
function useConnection(autoCheck = true) {
|
|
1939
2495
|
const { fetchWithAuth } = usePersonaContext();
|
|
1940
|
-
const [health, setHealth] =
|
|
1941
|
-
const [isConnected, setIsConnected] =
|
|
1942
|
-
const [isLoading, setIsLoading] =
|
|
1943
|
-
const checkHealth =
|
|
2496
|
+
const [health, setHealth] = useState8(null);
|
|
2497
|
+
const [isConnected, setIsConnected] = useState8(false);
|
|
2498
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
2499
|
+
const checkHealth = useCallback8(async () => {
|
|
1944
2500
|
setIsLoading(true);
|
|
1945
2501
|
try {
|
|
1946
2502
|
const res = await fetchWithAuth("/health");
|
|
@@ -1959,7 +2515,7 @@ function useConnection(autoCheck = true) {
|
|
|
1959
2515
|
setIsLoading(false);
|
|
1960
2516
|
}
|
|
1961
2517
|
}, [fetchWithAuth]);
|
|
1962
|
-
|
|
2518
|
+
useEffect8(() => {
|
|
1963
2519
|
if (autoCheck) {
|
|
1964
2520
|
void checkHealth();
|
|
1965
2521
|
}
|
|
@@ -1973,15 +2529,15 @@ function useConnection(autoCheck = true) {
|
|
|
1973
2529
|
}
|
|
1974
2530
|
|
|
1975
2531
|
// src/hooks/useMcpConnections.ts
|
|
1976
|
-
import { useCallback as
|
|
2532
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useState as useState9 } from "react";
|
|
1977
2533
|
function useMcpConnections(options = {}) {
|
|
1978
2534
|
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
1979
2535
|
const agentId = options.agentId ?? defaultAgentId;
|
|
1980
2536
|
const autoFetch = options.autoFetch ?? true;
|
|
1981
|
-
const [connections, setConnections] =
|
|
1982
|
-
const [isLoading, setIsLoading] =
|
|
1983
|
-
const [error, setError] =
|
|
1984
|
-
const fetchConnections =
|
|
2537
|
+
const [connections, setConnections] = useState9([]);
|
|
2538
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
2539
|
+
const [error, setError] = useState9(null);
|
|
2540
|
+
const fetchConnections = useCallback9(async () => {
|
|
1985
2541
|
if (!agentId) return [];
|
|
1986
2542
|
setIsLoading(true);
|
|
1987
2543
|
setError(null);
|
|
@@ -2005,7 +2561,7 @@ function useMcpConnections(options = {}) {
|
|
|
2005
2561
|
setIsLoading(false);
|
|
2006
2562
|
}
|
|
2007
2563
|
}, [agentId, fetchWithAuth, options.returnTo]);
|
|
2008
|
-
|
|
2564
|
+
useEffect9(() => {
|
|
2009
2565
|
if (autoFetch) void fetchConnections();
|
|
2010
2566
|
}, [autoFetch, fetchConnections]);
|
|
2011
2567
|
return {
|
|
@@ -2019,11 +2575,11 @@ function useMcpConnections(options = {}) {
|
|
|
2019
2575
|
}
|
|
2020
2576
|
|
|
2021
2577
|
// src/hooks/useMcp.ts
|
|
2022
|
-
import { useCallback as
|
|
2578
|
+
import { useCallback as useCallback10 } from "react";
|
|
2023
2579
|
function useMcp(options = {}) {
|
|
2024
2580
|
const { fetchWithAuth } = usePersonaContext();
|
|
2025
2581
|
const defaultMcpId = options.mcpId;
|
|
2026
|
-
const readResource =
|
|
2582
|
+
const readResource = useCallback10(
|
|
2027
2583
|
async (uri, mcpId) => {
|
|
2028
2584
|
const id = mcpId ?? defaultMcpId;
|
|
2029
2585
|
if (!id) throw new Error("MCP server ID is required to read resource");
|
|
@@ -2037,7 +2593,7 @@ function useMcp(options = {}) {
|
|
|
2037
2593
|
},
|
|
2038
2594
|
[fetchWithAuth, defaultMcpId]
|
|
2039
2595
|
);
|
|
2040
|
-
const callTool =
|
|
2596
|
+
const callTool = useCallback10(
|
|
2041
2597
|
async (name, args, mcpId) => {
|
|
2042
2598
|
const id = mcpId ?? defaultMcpId;
|
|
2043
2599
|
if (!id) throw new Error("MCP server ID is required to call tool");
|
|
@@ -2059,6 +2615,592 @@ function useMcp(options = {}) {
|
|
|
2059
2615
|
};
|
|
2060
2616
|
}
|
|
2061
2617
|
|
|
2618
|
+
// src/hooks/useWorkflows.ts
|
|
2619
|
+
import { useCallback as useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
|
|
2620
|
+
function useWorkflows(options) {
|
|
2621
|
+
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2622
|
+
const { autoFetch = true, search, status, page, limit } = opts;
|
|
2623
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
2624
|
+
const [workflows, setWorkflows] = useState10([]);
|
|
2625
|
+
const [pagination, setPagination] = useState10({
|
|
2626
|
+
page: page ?? 1,
|
|
2627
|
+
limit: limit ?? 20,
|
|
2628
|
+
total: 0,
|
|
2629
|
+
totalPages: 0
|
|
2630
|
+
});
|
|
2631
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
2632
|
+
const [error, setError] = useState10(null);
|
|
2633
|
+
const fetchWorkflows = useCallback11(async () => {
|
|
2634
|
+
setIsLoading(true);
|
|
2635
|
+
setError(null);
|
|
2636
|
+
try {
|
|
2637
|
+
const params = new URLSearchParams();
|
|
2638
|
+
if (search) params.set("search", search);
|
|
2639
|
+
if (status) params.set("status", status);
|
|
2640
|
+
if (page) params.set("page", String(page));
|
|
2641
|
+
if (limit) params.set("limit", String(limit));
|
|
2642
|
+
const queryStr = params.toString() ? `?${params.toString()}` : "";
|
|
2643
|
+
const res = await fetchWithAuth(`/workflows${queryStr}`);
|
|
2644
|
+
if (!res.ok) {
|
|
2645
|
+
throw new Error(`Failed to list workflows: ${res.statusText}`);
|
|
2646
|
+
}
|
|
2647
|
+
const data = await res.json();
|
|
2648
|
+
const items = Array.isArray(data) ? data : data?.items || data?.workflows || [];
|
|
2649
|
+
setWorkflows(items);
|
|
2650
|
+
if (data?.pagination) {
|
|
2651
|
+
setPagination({
|
|
2652
|
+
page: data.pagination.page ?? 1,
|
|
2653
|
+
limit: data.pagination.limit ?? items.length,
|
|
2654
|
+
total: data.pagination.total ?? items.length,
|
|
2655
|
+
totalPages: data.pagination.totalPages ?? 1
|
|
2656
|
+
});
|
|
2657
|
+
} else {
|
|
2658
|
+
setPagination({
|
|
2659
|
+
page: 1,
|
|
2660
|
+
limit: items.length,
|
|
2661
|
+
total: items.length,
|
|
2662
|
+
totalPages: 1
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
return items;
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2668
|
+
setError(errorObj);
|
|
2669
|
+
return [];
|
|
2670
|
+
} finally {
|
|
2671
|
+
setIsLoading(false);
|
|
2672
|
+
}
|
|
2673
|
+
}, [fetchWithAuth, search, status, page, limit]);
|
|
2674
|
+
const createWorkflow = useCallback11(
|
|
2675
|
+
async (input) => {
|
|
2676
|
+
setError(null);
|
|
2677
|
+
try {
|
|
2678
|
+
const res = await fetchWithAuth("/workflows", {
|
|
2679
|
+
method: "POST",
|
|
2680
|
+
headers: { "Content-Type": "application/json" },
|
|
2681
|
+
body: JSON.stringify(input)
|
|
2682
|
+
});
|
|
2683
|
+
if (!res.ok) {
|
|
2684
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2685
|
+
throw new Error(`Failed to create workflow (${res.status}): ${errText}`);
|
|
2686
|
+
}
|
|
2687
|
+
const created = await res.json();
|
|
2688
|
+
setWorkflows((prev) => [created, ...prev]);
|
|
2689
|
+
return created;
|
|
2690
|
+
} catch (err) {
|
|
2691
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2692
|
+
setError(errorObj);
|
|
2693
|
+
throw errorObj;
|
|
2694
|
+
}
|
|
2695
|
+
},
|
|
2696
|
+
[fetchWithAuth]
|
|
2697
|
+
);
|
|
2698
|
+
useEffect10(() => {
|
|
2699
|
+
if (autoFetch) {
|
|
2700
|
+
void fetchWorkflows();
|
|
2701
|
+
}
|
|
2702
|
+
}, [autoFetch, fetchWorkflows]);
|
|
2703
|
+
return {
|
|
2704
|
+
workflows,
|
|
2705
|
+
pagination,
|
|
2706
|
+
isLoading,
|
|
2707
|
+
error,
|
|
2708
|
+
refetch: fetchWorkflows,
|
|
2709
|
+
createWorkflow
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
// src/hooks/useWorkflow.ts
|
|
2714
|
+
import { useCallback as useCallback12, useEffect as useEffect11, useState as useState11 } from "react";
|
|
2715
|
+
function useWorkflow(workflowId, options) {
|
|
2716
|
+
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2717
|
+
const { autoFetch = true } = opts;
|
|
2718
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
2719
|
+
const [workflow, setWorkflow] = useState11(null);
|
|
2720
|
+
const [versions, setVersions] = useState11([]);
|
|
2721
|
+
const [mermaid, setMermaid] = useState11(null);
|
|
2722
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
2723
|
+
const [error, setError] = useState11(null);
|
|
2724
|
+
const fetchWorkflow = useCallback12(async () => {
|
|
2725
|
+
if (!workflowId) return null;
|
|
2726
|
+
setIsLoading(true);
|
|
2727
|
+
setError(null);
|
|
2728
|
+
try {
|
|
2729
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}`);
|
|
2730
|
+
if (!res.ok) {
|
|
2731
|
+
throw new Error(`Failed to fetch workflow (${res.status}): ${res.statusText}`);
|
|
2732
|
+
}
|
|
2733
|
+
const data = await res.json();
|
|
2734
|
+
setWorkflow(data);
|
|
2735
|
+
return data;
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2738
|
+
setError(errorObj);
|
|
2739
|
+
return null;
|
|
2740
|
+
} finally {
|
|
2741
|
+
setIsLoading(false);
|
|
2742
|
+
}
|
|
2743
|
+
}, [fetchWithAuth, workflowId]);
|
|
2744
|
+
const fetchVersions = useCallback12(async () => {
|
|
2745
|
+
if (!workflowId) return [];
|
|
2746
|
+
try {
|
|
2747
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}/versions`);
|
|
2748
|
+
if (!res.ok) return [];
|
|
2749
|
+
const data = await res.json();
|
|
2750
|
+
const items = Array.isArray(data) ? data : data?.items || [];
|
|
2751
|
+
setVersions(items);
|
|
2752
|
+
return items;
|
|
2753
|
+
} catch {
|
|
2754
|
+
return [];
|
|
2755
|
+
}
|
|
2756
|
+
}, [fetchWithAuth, workflowId]);
|
|
2757
|
+
const fetchMermaid = useCallback12(async () => {
|
|
2758
|
+
if (!workflowId) return "";
|
|
2759
|
+
try {
|
|
2760
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}/mermaid`);
|
|
2761
|
+
if (!res.ok) return "";
|
|
2762
|
+
const data = await res.json();
|
|
2763
|
+
const code = typeof data?.mermaid === "string" ? data.mermaid : String(data ?? "");
|
|
2764
|
+
setMermaid(code);
|
|
2765
|
+
return code;
|
|
2766
|
+
} catch {
|
|
2767
|
+
return "";
|
|
2768
|
+
}
|
|
2769
|
+
}, [fetchWithAuth, workflowId]);
|
|
2770
|
+
const saveDraft = useCallback12(
|
|
2771
|
+
async (draft) => {
|
|
2772
|
+
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2773
|
+
setError(null);
|
|
2774
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}/draft`, {
|
|
2775
|
+
method: "POST",
|
|
2776
|
+
headers: { "Content-Type": "application/json" },
|
|
2777
|
+
body: JSON.stringify({ draft })
|
|
2778
|
+
});
|
|
2779
|
+
if (!res.ok) {
|
|
2780
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2781
|
+
throw new Error(`Failed to save draft (${res.status}): ${errText}`);
|
|
2782
|
+
}
|
|
2783
|
+
const updated = await res.json();
|
|
2784
|
+
setWorkflow(updated);
|
|
2785
|
+
return updated;
|
|
2786
|
+
},
|
|
2787
|
+
[fetchWithAuth, workflowId]
|
|
2788
|
+
);
|
|
2789
|
+
const publish = useCallback12(
|
|
2790
|
+
async (summary) => {
|
|
2791
|
+
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2792
|
+
setError(null);
|
|
2793
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}/publish`, {
|
|
2794
|
+
method: "POST",
|
|
2795
|
+
headers: { "Content-Type": "application/json" },
|
|
2796
|
+
body: JSON.stringify(summary ? { summary } : {})
|
|
2797
|
+
});
|
|
2798
|
+
if (!res.ok) {
|
|
2799
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2800
|
+
throw new Error(`Failed to publish workflow (${res.status}): ${errText}`);
|
|
2801
|
+
}
|
|
2802
|
+
const versionResult = await res.json();
|
|
2803
|
+
void fetchWorkflow();
|
|
2804
|
+
void fetchVersions();
|
|
2805
|
+
return versionResult;
|
|
2806
|
+
},
|
|
2807
|
+
[fetchWithAuth, workflowId, fetchWorkflow, fetchVersions]
|
|
2808
|
+
);
|
|
2809
|
+
const deleteWorkflow = useCallback12(async () => {
|
|
2810
|
+
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2811
|
+
setError(null);
|
|
2812
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}`, {
|
|
2813
|
+
method: "DELETE"
|
|
2814
|
+
});
|
|
2815
|
+
if (!res.ok) {
|
|
2816
|
+
throw new Error(`Failed to delete workflow (${res.status}): ${res.statusText}`);
|
|
2817
|
+
}
|
|
2818
|
+
setWorkflow(null);
|
|
2819
|
+
}, [fetchWithAuth, workflowId]);
|
|
2820
|
+
useEffect11(() => {
|
|
2821
|
+
if (autoFetch && workflowId) {
|
|
2822
|
+
void fetchWorkflow();
|
|
2823
|
+
}
|
|
2824
|
+
}, [autoFetch, workflowId, fetchWorkflow]);
|
|
2825
|
+
return {
|
|
2826
|
+
workflow,
|
|
2827
|
+
versions,
|
|
2828
|
+
mermaid,
|
|
2829
|
+
isLoading,
|
|
2830
|
+
error,
|
|
2831
|
+
refetch: fetchWorkflow,
|
|
2832
|
+
fetchVersions,
|
|
2833
|
+
fetchMermaid,
|
|
2834
|
+
saveDraft,
|
|
2835
|
+
publish,
|
|
2836
|
+
deleteWorkflow
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// src/hooks/useWorkflowStream.ts
|
|
2841
|
+
import { useCallback as useCallback13, useEffect as useEffect12, useRef as useRef4, useState as useState12 } from "react";
|
|
2842
|
+
function useWorkflowStream(workflowId, options) {
|
|
2843
|
+
const { baseUrl, getAuthToken, fetchWithAuth, logger } = usePersonaContext();
|
|
2844
|
+
const [status, setStatus] = useState12("idle");
|
|
2845
|
+
const [runId, setRunId] = useState12(null);
|
|
2846
|
+
const [activeNodeId, setActiveNodeId] = useState12(null);
|
|
2847
|
+
const [nodeRuns, setNodeRuns] = useState12(
|
|
2848
|
+
{}
|
|
2849
|
+
);
|
|
2850
|
+
const [text, setText] = useState12("");
|
|
2851
|
+
const [output, setOutput] = useState12(null);
|
|
2852
|
+
const [error, setError] = useState12(null);
|
|
2853
|
+
const [events, setEvents] = useState12([]);
|
|
2854
|
+
const abortControllerRef = useRef4(null);
|
|
2855
|
+
const currentRunIdRef = useRef4(null);
|
|
2856
|
+
currentRunIdRef.current = runId;
|
|
2857
|
+
const optionsRef = useRef4(options);
|
|
2858
|
+
optionsRef.current = options;
|
|
2859
|
+
const cancel = useCallback13(async () => {
|
|
2860
|
+
if (abortControllerRef.current) {
|
|
2861
|
+
abortControllerRef.current.abort();
|
|
2862
|
+
abortControllerRef.current = null;
|
|
2863
|
+
}
|
|
2864
|
+
const currentRun = currentRunIdRef.current;
|
|
2865
|
+
if (currentRun) {
|
|
2866
|
+
try {
|
|
2867
|
+
await fetchWithAuth(`/workflows/runs/${currentRun}/cancel`, {
|
|
2868
|
+
method: "POST"
|
|
2869
|
+
});
|
|
2870
|
+
} catch (err) {
|
|
2871
|
+
logger.warn("Failed to send cancel request to workflow run", {
|
|
2872
|
+
runId: currentRun,
|
|
2873
|
+
error: String(err)
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
setStatus("cancelled");
|
|
2878
|
+
setActiveNodeId(null);
|
|
2879
|
+
}, [fetchWithAuth, logger]);
|
|
2880
|
+
const reset = useCallback13(() => {
|
|
2881
|
+
if (abortControllerRef.current) {
|
|
2882
|
+
abortControllerRef.current.abort();
|
|
2883
|
+
abortControllerRef.current = null;
|
|
2884
|
+
}
|
|
2885
|
+
setStatus("idle");
|
|
2886
|
+
setRunId(null);
|
|
2887
|
+
setActiveNodeId(null);
|
|
2888
|
+
setNodeRuns({});
|
|
2889
|
+
setText("");
|
|
2890
|
+
setOutput(null);
|
|
2891
|
+
setError(null);
|
|
2892
|
+
setEvents([]);
|
|
2893
|
+
}, []);
|
|
2894
|
+
const processStream = useCallback13(
|
|
2895
|
+
async (url, method, body, targetRunId) => {
|
|
2896
|
+
if (abortControllerRef.current) {
|
|
2897
|
+
abortControllerRef.current.abort();
|
|
2898
|
+
}
|
|
2899
|
+
const controller = new AbortController();
|
|
2900
|
+
abortControllerRef.current = controller;
|
|
2901
|
+
setStatus("running");
|
|
2902
|
+
setError(null);
|
|
2903
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
2904
|
+
const headers = {
|
|
2905
|
+
Accept: "text/event-stream"
|
|
2906
|
+
};
|
|
2907
|
+
if (token) {
|
|
2908
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
2909
|
+
}
|
|
2910
|
+
if (method === "POST") {
|
|
2911
|
+
headers["Content-Type"] = "application/json";
|
|
2912
|
+
}
|
|
2913
|
+
let assignedRunId = targetRunId ?? null;
|
|
2914
|
+
let accumulatedText = "";
|
|
2915
|
+
let finalOutput = null;
|
|
2916
|
+
try {
|
|
2917
|
+
const stream = await openSSEStream({
|
|
2918
|
+
url,
|
|
2919
|
+
method,
|
|
2920
|
+
headers,
|
|
2921
|
+
body,
|
|
2922
|
+
signal: controller.signal
|
|
2923
|
+
});
|
|
2924
|
+
if (!stream.ok) {
|
|
2925
|
+
throw new Error(
|
|
2926
|
+
stream.errorText || `Stream request failed with status ${stream.status}`
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
const runIdHeader = stream.getHeader("x-persona-run-id");
|
|
2930
|
+
if (runIdHeader) {
|
|
2931
|
+
assignedRunId = runIdHeader;
|
|
2932
|
+
setRunId(runIdHeader);
|
|
2933
|
+
}
|
|
2934
|
+
let buffer = "";
|
|
2935
|
+
while (true) {
|
|
2936
|
+
const { done, value } = await stream.reader.read();
|
|
2937
|
+
if (done) break;
|
|
2938
|
+
if (!value) continue;
|
|
2939
|
+
buffer += value;
|
|
2940
|
+
const lines = buffer.split("\n");
|
|
2941
|
+
buffer = lines.pop() ?? "";
|
|
2942
|
+
for (const line of lines) {
|
|
2943
|
+
const trimmed = line.trim();
|
|
2944
|
+
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
2945
|
+
if (trimmed.startsWith("data:")) {
|
|
2946
|
+
const rawData = trimmed.slice(5).trim();
|
|
2947
|
+
if (!rawData || rawData === "[DONE]") continue;
|
|
2948
|
+
try {
|
|
2949
|
+
const event = JSON.parse(rawData);
|
|
2950
|
+
setEvents((prev) => [...prev, event]);
|
|
2951
|
+
optionsRef.current?.onEvent?.(event);
|
|
2952
|
+
if ("runId" in event && typeof event.runId === "string" && !assignedRunId) {
|
|
2953
|
+
assignedRunId = event.runId;
|
|
2954
|
+
setRunId(event.runId);
|
|
2955
|
+
}
|
|
2956
|
+
if (event.type === "TEXT_MESSAGE_CHUNK") {
|
|
2957
|
+
accumulatedText += event.delta;
|
|
2958
|
+
setText((prev) => prev + event.delta);
|
|
2959
|
+
} else if (event.type === "CUSTOM") {
|
|
2960
|
+
if (event.name === "workflow_node_started") {
|
|
2961
|
+
const val = event.value;
|
|
2962
|
+
setActiveNodeId(val.nodeId);
|
|
2963
|
+
setNodeRuns((prev) => ({
|
|
2964
|
+
...prev,
|
|
2965
|
+
[val.nodeId]: {
|
|
2966
|
+
nodeId: val.nodeId,
|
|
2967
|
+
nodeType: val.nodeType,
|
|
2968
|
+
nodeLabel: val.nodeLabel,
|
|
2969
|
+
status: "running",
|
|
2970
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2971
|
+
input: val.input
|
|
2972
|
+
}
|
|
2973
|
+
}));
|
|
2974
|
+
optionsRef.current?.onNodeStarted?.(val);
|
|
2975
|
+
} else if (event.name === "workflow_node_completed") {
|
|
2976
|
+
const val = event.value;
|
|
2977
|
+
setNodeRuns((prev) => {
|
|
2978
|
+
const existing = prev[val.nodeId];
|
|
2979
|
+
return {
|
|
2980
|
+
...prev,
|
|
2981
|
+
[val.nodeId]: {
|
|
2982
|
+
nodeId: val.nodeId,
|
|
2983
|
+
nodeType: existing?.nodeType ?? "unknown",
|
|
2984
|
+
nodeLabel: existing?.nodeLabel ?? val.nodeId,
|
|
2985
|
+
status: "completed",
|
|
2986
|
+
startedAt: existing?.startedAt,
|
|
2987
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2988
|
+
input: existing?.input,
|
|
2989
|
+
output: val.output
|
|
2990
|
+
}
|
|
2991
|
+
};
|
|
2992
|
+
});
|
|
2993
|
+
setActiveNodeId((curr) => curr === val.nodeId ? null : curr);
|
|
2994
|
+
finalOutput = val.output;
|
|
2995
|
+
setOutput(val.output);
|
|
2996
|
+
optionsRef.current?.onNodeCompleted?.(val);
|
|
2997
|
+
} else if (event.name === "workflow_node_failed") {
|
|
2998
|
+
const val = event.value;
|
|
2999
|
+
setNodeRuns((prev) => {
|
|
3000
|
+
const existing = prev[val.nodeId];
|
|
3001
|
+
return {
|
|
3002
|
+
...prev,
|
|
3003
|
+
[val.nodeId]: {
|
|
3004
|
+
nodeId: val.nodeId,
|
|
3005
|
+
nodeType: existing?.nodeType ?? "unknown",
|
|
3006
|
+
nodeLabel: existing?.nodeLabel ?? val.nodeId,
|
|
3007
|
+
status: "failed",
|
|
3008
|
+
startedAt: existing?.startedAt,
|
|
3009
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3010
|
+
input: existing?.input,
|
|
3011
|
+
error: val.error
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
});
|
|
3015
|
+
setActiveNodeId((curr) => curr === val.nodeId ? null : curr);
|
|
3016
|
+
optionsRef.current?.onNodeFailed?.(val);
|
|
3017
|
+
}
|
|
3018
|
+
} else if (event.type === "RUN_FINISHED") {
|
|
3019
|
+
setStatus("completed");
|
|
3020
|
+
setActiveNodeId(null);
|
|
3021
|
+
optionsRef.current?.onFinish?.({
|
|
3022
|
+
runId: assignedRunId || "",
|
|
3023
|
+
output: finalOutput,
|
|
3024
|
+
text: accumulatedText
|
|
3025
|
+
});
|
|
3026
|
+
} else if (event.type === "RUN_ERROR") {
|
|
3027
|
+
const runErr = new Error(event.message || event.code || "Workflow run error");
|
|
3028
|
+
setStatus("failed");
|
|
3029
|
+
setError(runErr);
|
|
3030
|
+
setActiveNodeId(null);
|
|
3031
|
+
optionsRef.current?.onError?.(runErr);
|
|
3032
|
+
}
|
|
3033
|
+
} catch {
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
setStatus((prev) => prev === "running" ? "completed" : prev);
|
|
3039
|
+
return finalOutput;
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
if (controller.signal.aborted) {
|
|
3042
|
+
setStatus("cancelled");
|
|
3043
|
+
return null;
|
|
3044
|
+
}
|
|
3045
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3046
|
+
setStatus("failed");
|
|
3047
|
+
setError(errorObj);
|
|
3048
|
+
setActiveNodeId(null);
|
|
3049
|
+
optionsRef.current?.onError?.(errorObj);
|
|
3050
|
+
throw errorObj;
|
|
3051
|
+
} finally {
|
|
3052
|
+
if (abortControllerRef.current === controller) {
|
|
3053
|
+
abortControllerRef.current = null;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
[getAuthToken]
|
|
3058
|
+
);
|
|
3059
|
+
const start = useCallback13(
|
|
3060
|
+
async (input, opts) => {
|
|
3061
|
+
const targetWfId = opts?.workflowId || workflowId;
|
|
3062
|
+
if (!targetWfId) {
|
|
3063
|
+
throw new Error("Workflow ID must be provided to start execution");
|
|
3064
|
+
}
|
|
3065
|
+
setNodeRuns({});
|
|
3066
|
+
setText("");
|
|
3067
|
+
setOutput(null);
|
|
3068
|
+
setEvents([]);
|
|
3069
|
+
setActiveNodeId(null);
|
|
3070
|
+
const cleanBase = baseUrl.replace(/\/+$/, "");
|
|
3071
|
+
const url = `${cleanBase}/workflows/${targetWfId}/stream`;
|
|
3072
|
+
const body = JSON.stringify({
|
|
3073
|
+
input: input ?? {},
|
|
3074
|
+
dryRun: opts?.dryRun ?? false
|
|
3075
|
+
});
|
|
3076
|
+
return processStream(url, "POST", body);
|
|
3077
|
+
},
|
|
3078
|
+
[workflowId, baseUrl, processStream]
|
|
3079
|
+
);
|
|
3080
|
+
const resume = useCallback13(
|
|
3081
|
+
async (targetRunId, sinceSeq) => {
|
|
3082
|
+
if (!targetRunId) {
|
|
3083
|
+
throw new Error("Run ID is required to resume stream");
|
|
3084
|
+
}
|
|
3085
|
+
setRunId(targetRunId);
|
|
3086
|
+
const cleanBase = baseUrl.replace(/\/+$/, "");
|
|
3087
|
+
const query = sinceSeq !== void 0 ? `?since=${sinceSeq}` : "";
|
|
3088
|
+
const url = `${cleanBase}/workflows/runs/${targetRunId}/resume${query}`;
|
|
3089
|
+
await processStream(url, "GET", void 0, targetRunId);
|
|
3090
|
+
},
|
|
3091
|
+
[baseUrl, processStream]
|
|
3092
|
+
);
|
|
3093
|
+
useEffect12(() => {
|
|
3094
|
+
return () => {
|
|
3095
|
+
if (abortControllerRef.current) {
|
|
3096
|
+
abortControllerRef.current.abort();
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
}, []);
|
|
3100
|
+
return {
|
|
3101
|
+
runId,
|
|
3102
|
+
status,
|
|
3103
|
+
isRunning: status === "running",
|
|
3104
|
+
activeNodeId,
|
|
3105
|
+
nodeRuns,
|
|
3106
|
+
text,
|
|
3107
|
+
output,
|
|
3108
|
+
error,
|
|
3109
|
+
events,
|
|
3110
|
+
start,
|
|
3111
|
+
cancel,
|
|
3112
|
+
resume,
|
|
3113
|
+
reset
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
// src/hooks/useWorkflowRuns.ts
|
|
3118
|
+
import { useCallback as useCallback14, useEffect as useEffect13, useState as useState13 } from "react";
|
|
3119
|
+
function useWorkflowRuns(workflowId, options) {
|
|
3120
|
+
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
3121
|
+
const { autoFetch = true, page = 1, limit = 20 } = opts;
|
|
3122
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
3123
|
+
const [runs, setRuns] = useState13([]);
|
|
3124
|
+
const [pagination, setPagination] = useState13({
|
|
3125
|
+
page,
|
|
3126
|
+
limit,
|
|
3127
|
+
total: 0,
|
|
3128
|
+
totalPages: 0
|
|
3129
|
+
});
|
|
3130
|
+
const [isLoading, setIsLoading] = useState13(false);
|
|
3131
|
+
const [error, setError] = useState13(null);
|
|
3132
|
+
const fetchRuns = useCallback14(async () => {
|
|
3133
|
+
if (!workflowId) return [];
|
|
3134
|
+
setIsLoading(true);
|
|
3135
|
+
setError(null);
|
|
3136
|
+
try {
|
|
3137
|
+
const query = `?page=${page}&limit=${limit}`;
|
|
3138
|
+
const res = await fetchWithAuth(`/workflows/${workflowId}/runs${query}`);
|
|
3139
|
+
if (!res.ok) {
|
|
3140
|
+
throw new Error(`Failed to list workflow runs: ${res.statusText}`);
|
|
3141
|
+
}
|
|
3142
|
+
const data = await res.json();
|
|
3143
|
+
const items = Array.isArray(data) ? data : data?.items || data?.runs || [];
|
|
3144
|
+
setRuns(items);
|
|
3145
|
+
if (data?.pagination) {
|
|
3146
|
+
setPagination({
|
|
3147
|
+
page: data.pagination.page ?? page,
|
|
3148
|
+
limit: data.pagination.limit ?? limit,
|
|
3149
|
+
total: data.pagination.total ?? items.length,
|
|
3150
|
+
totalPages: data.pagination.totalPages ?? 1
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
return items;
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3156
|
+
setError(errorObj);
|
|
3157
|
+
return [];
|
|
3158
|
+
} finally {
|
|
3159
|
+
setIsLoading(false);
|
|
3160
|
+
}
|
|
3161
|
+
}, [fetchWithAuth, workflowId, page, limit]);
|
|
3162
|
+
const getRun = useCallback14(
|
|
3163
|
+
async (runId) => {
|
|
3164
|
+
if (!runId) throw new Error("Run ID is required");
|
|
3165
|
+
const res = await fetchWithAuth(`/workflows/runs/${runId}`);
|
|
3166
|
+
if (!res.ok) {
|
|
3167
|
+
throw new Error(`Failed to fetch workflow run (${res.status}): ${res.statusText}`);
|
|
3168
|
+
}
|
|
3169
|
+
return await res.json();
|
|
3170
|
+
},
|
|
3171
|
+
[fetchWithAuth]
|
|
3172
|
+
);
|
|
3173
|
+
const cancelRun = useCallback14(
|
|
3174
|
+
async (runId) => {
|
|
3175
|
+
if (!runId) throw new Error("Run ID is required");
|
|
3176
|
+
const res = await fetchWithAuth(`/workflows/runs/${runId}/cancel`, {
|
|
3177
|
+
method: "POST"
|
|
3178
|
+
});
|
|
3179
|
+
if (!res.ok) {
|
|
3180
|
+
throw new Error(`Failed to cancel workflow run (${res.status}): ${res.statusText}`);
|
|
3181
|
+
}
|
|
3182
|
+
setRuns(
|
|
3183
|
+
(prev) => prev.map((r) => r._id === runId ? { ...r, status: "cancelled" } : r)
|
|
3184
|
+
);
|
|
3185
|
+
},
|
|
3186
|
+
[fetchWithAuth]
|
|
3187
|
+
);
|
|
3188
|
+
useEffect13(() => {
|
|
3189
|
+
if (autoFetch && workflowId) {
|
|
3190
|
+
void fetchRuns();
|
|
3191
|
+
}
|
|
3192
|
+
}, [autoFetch, workflowId, fetchRuns]);
|
|
3193
|
+
return {
|
|
3194
|
+
runs,
|
|
3195
|
+
pagination,
|
|
3196
|
+
isLoading,
|
|
3197
|
+
error,
|
|
3198
|
+
refetch: fetchRuns,
|
|
3199
|
+
getRun,
|
|
3200
|
+
cancelRun
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
3203
|
+
|
|
2062
3204
|
// src/index.ts
|
|
2063
3205
|
import {
|
|
2064
3206
|
createLogger as createLogger2,
|
|
@@ -2067,8 +3209,9 @@ import {
|
|
|
2067
3209
|
getLogLevel,
|
|
2068
3210
|
isLevelEnabled
|
|
2069
3211
|
} from "@personaai/logger";
|
|
2070
|
-
var VERSION = "0.
|
|
3212
|
+
var VERSION = "0.10.0";
|
|
2071
3213
|
export {
|
|
3214
|
+
ARCHITECT_AGENT_ID,
|
|
2072
3215
|
PersonaProvider,
|
|
2073
3216
|
VERSION,
|
|
2074
3217
|
createLogger2 as createLogger,
|
|
@@ -2079,6 +3222,7 @@ export {
|
|
|
2079
3222
|
setLogLevel,
|
|
2080
3223
|
supportsStreamingFetch,
|
|
2081
3224
|
useAgents,
|
|
3225
|
+
useArchitectChat,
|
|
2082
3226
|
useChat,
|
|
2083
3227
|
useConnection,
|
|
2084
3228
|
useFiles,
|
|
@@ -2087,6 +3231,10 @@ export {
|
|
|
2087
3231
|
useMemory,
|
|
2088
3232
|
usePersonaContext,
|
|
2089
3233
|
useThreads,
|
|
2090
|
-
useVoice
|
|
3234
|
+
useVoice,
|
|
3235
|
+
useWorkflow,
|
|
3236
|
+
useWorkflowRuns,
|
|
3237
|
+
useWorkflowStream,
|
|
3238
|
+
useWorkflows
|
|
2091
3239
|
};
|
|
2092
3240
|
//# sourceMappingURL=index.js.map
|