@personaai/react 0.5.4 → 0.7.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 +34 -9
- package/dist/index.cjs +473 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +471 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ npm install @personaai/react
|
|
|
13
13
|
## Quickstart
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
|
-
import { PersonaProvider, useChat } from
|
|
16
|
+
import { PersonaProvider, useChat } from "@personaai/react";
|
|
17
17
|
|
|
18
18
|
function App() {
|
|
19
19
|
return (
|
|
@@ -46,14 +46,39 @@ function Chat() {
|
|
|
46
46
|
|
|
47
47
|
## Hooks
|
|
48
48
|
|
|
49
|
-
| Hook
|
|
50
|
-
|
|
|
51
|
-
| `useChat`
|
|
52
|
-
| `useThreads`
|
|
53
|
-
| `useFiles`
|
|
54
|
-
| `useMemory`
|
|
55
|
-
| `useAgents`
|
|
56
|
-
| `useConnection` | Health check — backend connectivity status
|
|
49
|
+
| Hook | Purpose |
|
|
50
|
+
| --------------- | -------------------------------------------------------------------------- |
|
|
51
|
+
| `useChat` | Streaming chat — messages, send, stop, reload, interrupts, workspace files |
|
|
52
|
+
| `useThreads` | Thread CRUD — list, create, delete, rename, reset, archive |
|
|
53
|
+
| `useFiles` | Upload management — list, upload, delete |
|
|
54
|
+
| `useMemory` | Persistent memory — read, write, delete |
|
|
55
|
+
| `useAgents` | Agent discovery — list available agents |
|
|
56
|
+
| `useConnection` | Health check — backend connectivity status |
|
|
57
|
+
|
|
58
|
+
## Devtools
|
|
59
|
+
|
|
60
|
+
Floating panel for local debugging — inspect hooks, messages and threads with zero runtime changes. Dev-only, not bundled to production unless imported.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install -D @personaai/devtools
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { PersonaDevtools } from "@personaai/devtools/react";
|
|
68
|
+
import { useChat, useThreads } from "@personaai/react";
|
|
69
|
+
|
|
70
|
+
function Devtools() {
|
|
71
|
+
const { threads } = useThreads();
|
|
72
|
+
const { messages, files, todos } = useChat();
|
|
73
|
+
return <PersonaDevtools clientState={{ threads, messages, files, todos }} />;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
process.env.NODE_ENV === "development" && <Devtools />;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
See `@personaai/devtools` `sdk/devtools/README.md` for `core`/`react`/`nextjs` subpaths and `baseUrl` polling option.
|
|
57
82
|
|
|
58
83
|
## Full documentation
|
|
59
84
|
|
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,8 @@ __export(index_exports, {
|
|
|
36
36
|
useMcpConnections: () => useMcpConnections,
|
|
37
37
|
useMemory: () => useMemory,
|
|
38
38
|
usePersonaContext: () => usePersonaContext,
|
|
39
|
-
useThreads: () => useThreads
|
|
39
|
+
useThreads: () => useThreads,
|
|
40
|
+
useVoice: () => useVoice
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(index_exports);
|
|
42
43
|
|
|
@@ -1207,6 +1208,21 @@ function useThreads(autoFetch = true) {
|
|
|
1207
1208
|
(threadId, title) => updateThread(threadId, { title }),
|
|
1208
1209
|
[updateThread]
|
|
1209
1210
|
);
|
|
1211
|
+
const resetThread = (0, import_react4.useCallback)(
|
|
1212
|
+
async (threadId) => {
|
|
1213
|
+
const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
|
|
1214
|
+
method: "POST"
|
|
1215
|
+
});
|
|
1216
|
+
if (!res.ok) throw new Error(`Failed to reset thread: ${res.statusText}`);
|
|
1217
|
+
const data = await res.json();
|
|
1218
|
+
const reset = data?.data ?? data;
|
|
1219
|
+
setThreads(
|
|
1220
|
+
(prev) => prev.map((t) => t._id === threadId ? { ...t, ...reset } : t)
|
|
1221
|
+
);
|
|
1222
|
+
return reset;
|
|
1223
|
+
},
|
|
1224
|
+
[fetchWithAuth]
|
|
1225
|
+
);
|
|
1210
1226
|
const getThread = (0, import_react4.useCallback)(
|
|
1211
1227
|
async (threadId) => {
|
|
1212
1228
|
const res = await fetchWithAuth(`/threads/${threadId}`);
|
|
@@ -1232,19 +1248,443 @@ function useThreads(autoFetch = true) {
|
|
|
1232
1248
|
deleteAllThreads,
|
|
1233
1249
|
updateThread,
|
|
1234
1250
|
renameThread,
|
|
1251
|
+
resetThread,
|
|
1235
1252
|
getThread
|
|
1236
1253
|
};
|
|
1237
1254
|
}
|
|
1238
1255
|
|
|
1239
|
-
// src/hooks/
|
|
1256
|
+
// src/hooks/useVoice.ts
|
|
1240
1257
|
var import_react5 = require("react");
|
|
1258
|
+
|
|
1259
|
+
// src/hooks/voiceWorklets.ts
|
|
1260
|
+
var RECORDER_WORKLET_SOURCE = `
|
|
1261
|
+
class PCMRecorderProcessor extends AudioWorkletProcessor {
|
|
1262
|
+
constructor() {
|
|
1263
|
+
super();
|
|
1264
|
+
this._buffer = [];
|
|
1265
|
+
this._chunkSamples = 320; // 20ms @ 16kHz
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
process(inputs) {
|
|
1269
|
+
const input = inputs[0];
|
|
1270
|
+
const channel = input && input[0];
|
|
1271
|
+
if (channel) {
|
|
1272
|
+
for (let i = 0; i < channel.length; i++) {
|
|
1273
|
+
this._buffer.push(channel[i]);
|
|
1274
|
+
}
|
|
1275
|
+
while (this._buffer.length >= this._chunkSamples) {
|
|
1276
|
+
const chunk = this._buffer.splice(0, this._chunkSamples);
|
|
1277
|
+
const int16 = new Int16Array(chunk.length);
|
|
1278
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
1279
|
+
const s = Math.max(-1, Math.min(1, chunk[i]));
|
|
1280
|
+
int16[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
|
|
1281
|
+
}
|
|
1282
|
+
this.port.postMessage(int16.buffer, [int16.buffer]);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
registerProcessor('pcm-recorder-processor', PCMRecorderProcessor);
|
|
1290
|
+
`;
|
|
1291
|
+
var PLAYER_WORKLET_SOURCE = `
|
|
1292
|
+
class PCMPlayerProcessor extends AudioWorkletProcessor {
|
|
1293
|
+
constructor() {
|
|
1294
|
+
super();
|
|
1295
|
+
this._queue = [];
|
|
1296
|
+
this._readOffset = 0;
|
|
1297
|
+
this.port.onmessage = (event) => {
|
|
1298
|
+
const msg = event.data;
|
|
1299
|
+
if (msg && msg.type === 'flush') {
|
|
1300
|
+
this._queue = [];
|
|
1301
|
+
this._readOffset = 0;
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const int16 = new Int16Array(msg);
|
|
1305
|
+
const float32 = new Float32Array(int16.length);
|
|
1306
|
+
for (let i = 0; i < int16.length; i++) {
|
|
1307
|
+
float32[i] = int16[i] / 0x8000;
|
|
1308
|
+
}
|
|
1309
|
+
this._queue.push(float32);
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
process(_inputs, outputs) {
|
|
1314
|
+
const output = outputs[0][0];
|
|
1315
|
+
let outIdx = 0;
|
|
1316
|
+
while (outIdx < output.length) {
|
|
1317
|
+
if (this._queue.length === 0) {
|
|
1318
|
+
output[outIdx++] = 0;
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
const current = this._queue[0];
|
|
1322
|
+
const remaining = current.length - this._readOffset;
|
|
1323
|
+
const toCopy = Math.min(remaining, output.length - outIdx);
|
|
1324
|
+
output.set(current.subarray(this._readOffset, this._readOffset + toCopy), outIdx);
|
|
1325
|
+
outIdx += toCopy;
|
|
1326
|
+
this._readOffset += toCopy;
|
|
1327
|
+
if (this._readOffset >= current.length) {
|
|
1328
|
+
this._queue.shift();
|
|
1329
|
+
this._readOffset = 0;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
registerProcessor('pcm-player-processor', PCMPlayerProcessor);
|
|
1337
|
+
`;
|
|
1338
|
+
function toModuleUrl(source) {
|
|
1339
|
+
const blob = new Blob([source], { type: "application/javascript" });
|
|
1340
|
+
return URL.createObjectURL(blob);
|
|
1341
|
+
}
|
|
1342
|
+
function recorderWorkletUrl() {
|
|
1343
|
+
return toModuleUrl(RECORDER_WORKLET_SOURCE);
|
|
1344
|
+
}
|
|
1345
|
+
function playerWorkletUrl() {
|
|
1346
|
+
return toModuleUrl(PLAYER_WORKLET_SOURCE);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// src/hooks/useVoice.ts
|
|
1350
|
+
function useVoice(options = {}) {
|
|
1351
|
+
const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
|
|
1352
|
+
const voiceLogger = (0, import_react5.useMemo)(() => logger.child("voice"), [logger]);
|
|
1353
|
+
const agentId = options.agentId || defaultAgentId;
|
|
1354
|
+
const [state, setState] = (0, import_react5.useState)("idle");
|
|
1355
|
+
const [isMuted, setIsMuted] = (0, import_react5.useState)(false);
|
|
1356
|
+
const [transcript, setTranscript] = (0, import_react5.useState)(
|
|
1357
|
+
[]
|
|
1358
|
+
);
|
|
1359
|
+
const [partial, setPartial] = (0, import_react5.useState)(
|
|
1360
|
+
null
|
|
1361
|
+
);
|
|
1362
|
+
const [toolCalls, setToolCalls] = (0, import_react5.useState)([]);
|
|
1363
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
1364
|
+
const [endReason, setEndReason] = (0, import_react5.useState)(
|
|
1365
|
+
null
|
|
1366
|
+
);
|
|
1367
|
+
const wsRef = (0, import_react5.useRef)(null);
|
|
1368
|
+
const streamRef = (0, import_react5.useRef)(null);
|
|
1369
|
+
const inputCtxRef = (0, import_react5.useRef)(null);
|
|
1370
|
+
const outputCtxRef = (0, import_react5.useRef)(null);
|
|
1371
|
+
const recorderNodeRef = (0, import_react5.useRef)(null);
|
|
1372
|
+
const playerNodeRef = (0, import_react5.useRef)(null);
|
|
1373
|
+
const acceptedTurnSeqRef = (0, import_react5.useRef)(0);
|
|
1374
|
+
const mountedRef = (0, import_react5.useRef)(true);
|
|
1375
|
+
(0, import_react5.useEffect)(() => {
|
|
1376
|
+
mountedRef.current = true;
|
|
1377
|
+
return () => {
|
|
1378
|
+
mountedRef.current = false;
|
|
1379
|
+
};
|
|
1380
|
+
}, []);
|
|
1381
|
+
const teardownAudio = (0, import_react5.useCallback)(() => {
|
|
1382
|
+
try {
|
|
1383
|
+
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
streamRef.current = null;
|
|
1387
|
+
try {
|
|
1388
|
+
recorderNodeRef.current?.disconnect();
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
recorderNodeRef.current = null;
|
|
1392
|
+
try {
|
|
1393
|
+
playerNodeRef.current?.disconnect();
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
playerNodeRef.current = null;
|
|
1397
|
+
try {
|
|
1398
|
+
void inputCtxRef.current?.close();
|
|
1399
|
+
} catch {
|
|
1400
|
+
}
|
|
1401
|
+
inputCtxRef.current = null;
|
|
1402
|
+
try {
|
|
1403
|
+
void outputCtxRef.current?.close();
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
outputCtxRef.current = null;
|
|
1407
|
+
}, []);
|
|
1408
|
+
const stop = (0, import_react5.useCallback)(() => {
|
|
1409
|
+
voiceLogger.debug("stop() called");
|
|
1410
|
+
try {
|
|
1411
|
+
wsRef.current?.close(1e3, "client_stop");
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1414
|
+
wsRef.current = null;
|
|
1415
|
+
teardownAudio();
|
|
1416
|
+
if (mountedRef.current) setState("idle");
|
|
1417
|
+
}, [teardownAudio, voiceLogger]);
|
|
1418
|
+
(0, import_react5.useEffect)(() => stop, [stop]);
|
|
1419
|
+
const startCapture = (0, import_react5.useCallback)(async (inputSampleRate) => {
|
|
1420
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1421
|
+
audio: {
|
|
1422
|
+
echoCancellation: true,
|
|
1423
|
+
noiseSuppression: true,
|
|
1424
|
+
autoGainControl: true,
|
|
1425
|
+
channelCount: 1
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
streamRef.current = stream;
|
|
1429
|
+
const inputCtx = new AudioContext({ sampleRate: inputSampleRate });
|
|
1430
|
+
inputCtxRef.current = inputCtx;
|
|
1431
|
+
await inputCtx.audioWorklet.addModule(recorderWorkletUrl());
|
|
1432
|
+
const source = inputCtx.createMediaStreamSource(stream);
|
|
1433
|
+
const recorderNode = new AudioWorkletNode(
|
|
1434
|
+
inputCtx,
|
|
1435
|
+
"pcm-recorder-processor"
|
|
1436
|
+
);
|
|
1437
|
+
recorderNode.port.onmessage = (event) => {
|
|
1438
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
1439
|
+
wsRef.current.send(event.data);
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
recorderNodeRef.current = recorderNode;
|
|
1443
|
+
source.connect(recorderNode);
|
|
1444
|
+
}, []);
|
|
1445
|
+
const setupPlayback = (0, import_react5.useCallback)(async (outputSampleRate) => {
|
|
1446
|
+
const outputCtx = new AudioContext({ sampleRate: outputSampleRate });
|
|
1447
|
+
outputCtxRef.current = outputCtx;
|
|
1448
|
+
await outputCtx.audioWorklet.addModule(playerWorkletUrl());
|
|
1449
|
+
const playerNode = new AudioWorkletNode(outputCtx, "pcm-player-processor");
|
|
1450
|
+
playerNode.connect(outputCtx.destination);
|
|
1451
|
+
playerNodeRef.current = playerNode;
|
|
1452
|
+
}, []);
|
|
1453
|
+
const upsertToolCall = (0, import_react5.useCallback)(
|
|
1454
|
+
(id, patch) => {
|
|
1455
|
+
setToolCalls((prev) => {
|
|
1456
|
+
const idx = prev.findIndex((t) => t.id === id);
|
|
1457
|
+
if (idx === -1)
|
|
1458
|
+
return [...prev, { id, status: "running", ...patch }];
|
|
1459
|
+
const next = [...prev];
|
|
1460
|
+
next[idx] = { ...next[idx], ...patch };
|
|
1461
|
+
return next;
|
|
1462
|
+
});
|
|
1463
|
+
},
|
|
1464
|
+
[]
|
|
1465
|
+
);
|
|
1466
|
+
const handleTranscript = (0, import_react5.useCallback)(
|
|
1467
|
+
(value) => {
|
|
1468
|
+
const { speaker, text, isFinal } = value;
|
|
1469
|
+
if (isFinal) {
|
|
1470
|
+
setPartial((prev) => prev?.speaker === speaker ? null : prev);
|
|
1471
|
+
setTranscript((prev) => [
|
|
1472
|
+
...prev,
|
|
1473
|
+
{ id: `${speaker}-${prev.length}`, speaker, text }
|
|
1474
|
+
]);
|
|
1475
|
+
} else {
|
|
1476
|
+
setPartial({ id: "partial", speaker, text });
|
|
1477
|
+
}
|
|
1478
|
+
},
|
|
1479
|
+
[]
|
|
1480
|
+
);
|
|
1481
|
+
const handleCustomEvent = (0, import_react5.useCallback)(
|
|
1482
|
+
(name, value) => {
|
|
1483
|
+
switch (name) {
|
|
1484
|
+
case "voice_session_ready":
|
|
1485
|
+
voiceLogger.debug("voice_session_ready", value);
|
|
1486
|
+
Promise.all([
|
|
1487
|
+
setupPlayback(value.outputSampleRate),
|
|
1488
|
+
startCapture(value.inputSampleRate)
|
|
1489
|
+
]).then(() => {
|
|
1490
|
+
if (mountedRef.current) setState("listening");
|
|
1491
|
+
}).catch((err) => {
|
|
1492
|
+
voiceLogger.error("audio graph setup failed", {
|
|
1493
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1494
|
+
});
|
|
1495
|
+
if (mountedRef.current) {
|
|
1496
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1497
|
+
setState("error");
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
break;
|
|
1501
|
+
case "voice_activity":
|
|
1502
|
+
if (value.speaker === "user" && value.state === "start") {
|
|
1503
|
+
setState("listening");
|
|
1504
|
+
} else if (value.speaker === "user" && value.state === "end") {
|
|
1505
|
+
setState("thinking");
|
|
1506
|
+
} else if (value.speaker === "agent" && value.state === "start") {
|
|
1507
|
+
setState("speaking");
|
|
1508
|
+
} else if (value.speaker === "agent" && value.state === "end") {
|
|
1509
|
+
setState("listening");
|
|
1510
|
+
}
|
|
1511
|
+
break;
|
|
1512
|
+
case "voice_transcript":
|
|
1513
|
+
handleTranscript(
|
|
1514
|
+
value
|
|
1515
|
+
);
|
|
1516
|
+
break;
|
|
1517
|
+
case "voice_interrupted":
|
|
1518
|
+
acceptedTurnSeqRef.current = value.turnSeq;
|
|
1519
|
+
playerNodeRef.current?.port.postMessage({ type: "flush" });
|
|
1520
|
+
break;
|
|
1521
|
+
case "voice_session_resumed":
|
|
1522
|
+
voiceLogger.info("session transparently resumed");
|
|
1523
|
+
break;
|
|
1524
|
+
case "voice_session_ended":
|
|
1525
|
+
voiceLogger.info("session ended", { reason: value.reason });
|
|
1526
|
+
setState("ended");
|
|
1527
|
+
setEndReason(value.reason ?? null);
|
|
1528
|
+
teardownAudio();
|
|
1529
|
+
try {
|
|
1530
|
+
wsRef.current?.close();
|
|
1531
|
+
} catch {
|
|
1532
|
+
}
|
|
1533
|
+
wsRef.current = null;
|
|
1534
|
+
break;
|
|
1535
|
+
default:
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
[handleTranscript, setupPlayback, startCapture, teardownAudio, voiceLogger]
|
|
1540
|
+
);
|
|
1541
|
+
const handleMessage = (0, import_react5.useCallback)(
|
|
1542
|
+
(event) => {
|
|
1543
|
+
if (typeof event.data !== "string") {
|
|
1544
|
+
const buf = event.data;
|
|
1545
|
+
const view = new DataView(buf);
|
|
1546
|
+
const turnSeq = view.getUint32(0, true);
|
|
1547
|
+
if (turnSeq >= acceptedTurnSeqRef.current) {
|
|
1548
|
+
acceptedTurnSeqRef.current = turnSeq;
|
|
1549
|
+
const pcmBytes = buf.slice(4);
|
|
1550
|
+
playerNodeRef.current?.port.postMessage(pcmBytes, [pcmBytes]);
|
|
1551
|
+
}
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
let msg;
|
|
1555
|
+
try {
|
|
1556
|
+
msg = JSON.parse(event.data);
|
|
1557
|
+
} catch {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
switch (msg.type) {
|
|
1561
|
+
case "CUSTOM":
|
|
1562
|
+
handleCustomEvent(
|
|
1563
|
+
msg.name,
|
|
1564
|
+
msg.value
|
|
1565
|
+
);
|
|
1566
|
+
break;
|
|
1567
|
+
case "TOOL_CALL_CHUNK":
|
|
1568
|
+
upsertToolCall(msg.toolCallId, {
|
|
1569
|
+
name: msg.toolCallName,
|
|
1570
|
+
status: "running"
|
|
1571
|
+
});
|
|
1572
|
+
break;
|
|
1573
|
+
case "TOOL_CALL_RESULT": {
|
|
1574
|
+
let parsed = null;
|
|
1575
|
+
try {
|
|
1576
|
+
parsed = JSON.parse(msg.content);
|
|
1577
|
+
} catch {
|
|
1578
|
+
parsed = { output: msg.content };
|
|
1579
|
+
}
|
|
1580
|
+
upsertToolCall(msg.toolCallId, {
|
|
1581
|
+
status: parsed?.error ? "error" : "done",
|
|
1582
|
+
summary: parsed?.error || (typeof parsed?.output === "string" ? parsed.output : JSON.stringify(parsed?.output ?? ""))
|
|
1583
|
+
});
|
|
1584
|
+
break;
|
|
1585
|
+
}
|
|
1586
|
+
case "RUN_ERROR":
|
|
1587
|
+
voiceLogger.warn("RUN_ERROR", { message: msg.message });
|
|
1588
|
+
setError(new Error(msg.message || "The agent run failed."));
|
|
1589
|
+
setState("error");
|
|
1590
|
+
break;
|
|
1591
|
+
default:
|
|
1592
|
+
break;
|
|
1593
|
+
}
|
|
1594
|
+
},
|
|
1595
|
+
[handleCustomEvent, upsertToolCall, voiceLogger]
|
|
1596
|
+
);
|
|
1597
|
+
const start = (0, import_react5.useCallback)(async () => {
|
|
1598
|
+
if (!agentId) {
|
|
1599
|
+
const err = new Error(
|
|
1600
|
+
"useVoice: no agentId provided and no defaultAgentId set on PersonaProvider"
|
|
1601
|
+
);
|
|
1602
|
+
setError(err);
|
|
1603
|
+
setState("error");
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
setError(null);
|
|
1607
|
+
setEndReason(null);
|
|
1608
|
+
setTranscript([]);
|
|
1609
|
+
setPartial(null);
|
|
1610
|
+
setToolCalls([]);
|
|
1611
|
+
acceptedTurnSeqRef.current = 0;
|
|
1612
|
+
setState("connecting");
|
|
1613
|
+
voiceLogger.debug("start() called", { agentId });
|
|
1614
|
+
try {
|
|
1615
|
+
const res = await fetchWithAuth("/voice/sessions", {
|
|
1616
|
+
method: "POST",
|
|
1617
|
+
headers: { "Content-Type": "application/json" },
|
|
1618
|
+
body: JSON.stringify({ agentId })
|
|
1619
|
+
});
|
|
1620
|
+
if (!res.ok) {
|
|
1621
|
+
throw new Error(`Failed to start voice session: ${res.statusText}`);
|
|
1622
|
+
}
|
|
1623
|
+
const data = await res.json();
|
|
1624
|
+
const ticket = data?.data ?? data;
|
|
1625
|
+
if (!ticket?.wsUrl) {
|
|
1626
|
+
throw new Error("Server did not return a voice session URL.");
|
|
1627
|
+
}
|
|
1628
|
+
const ws = new WebSocket(ticket.wsUrl);
|
|
1629
|
+
ws.binaryType = "arraybuffer";
|
|
1630
|
+
wsRef.current = ws;
|
|
1631
|
+
ws.onmessage = handleMessage;
|
|
1632
|
+
ws.onerror = () => {
|
|
1633
|
+
voiceLogger.warn("WebSocket error");
|
|
1634
|
+
if (mountedRef.current) {
|
|
1635
|
+
setError(new Error("Voice connection error."));
|
|
1636
|
+
setState("error");
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
ws.onclose = () => {
|
|
1640
|
+
if (mountedRef.current && wsRef.current === ws) {
|
|
1641
|
+
setState((prev) => prev === "error" ? prev : "idle");
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
} catch (err) {
|
|
1645
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1646
|
+
voiceLogger.error("start() failed", { error: errorObj.message });
|
|
1647
|
+
if (mountedRef.current) {
|
|
1648
|
+
setError(errorObj);
|
|
1649
|
+
setState("error");
|
|
1650
|
+
}
|
|
1651
|
+
teardownAudio();
|
|
1652
|
+
}
|
|
1653
|
+
}, [agentId, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
|
|
1654
|
+
const mute = (0, import_react5.useCallback)((muted) => {
|
|
1655
|
+
setIsMuted(muted);
|
|
1656
|
+
streamRef.current?.getAudioTracks().forEach((track) => {
|
|
1657
|
+
track.enabled = !muted;
|
|
1658
|
+
});
|
|
1659
|
+
}, []);
|
|
1660
|
+
const sendText = (0, import_react5.useCallback)((text) => {
|
|
1661
|
+
if (!text?.trim() || wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1662
|
+
wsRef.current.send(JSON.stringify({ type: "voice.text", text }));
|
|
1663
|
+
}, []);
|
|
1664
|
+
return {
|
|
1665
|
+
state,
|
|
1666
|
+
isMuted,
|
|
1667
|
+
transcript,
|
|
1668
|
+
partial,
|
|
1669
|
+
toolCalls,
|
|
1670
|
+
error,
|
|
1671
|
+
endReason,
|
|
1672
|
+
start,
|
|
1673
|
+
stop,
|
|
1674
|
+
mute,
|
|
1675
|
+
sendText
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
// src/hooks/useFiles.ts
|
|
1680
|
+
var import_react6 = require("react");
|
|
1241
1681
|
function useFiles(autoFetch = true) {
|
|
1242
1682
|
const { fetchWithAuth } = usePersonaContext();
|
|
1243
|
-
const [files, setFiles] = (0,
|
|
1244
|
-
const [isLoading, setIsLoading] = (0,
|
|
1245
|
-
const [isUploading, setIsUploading] = (0,
|
|
1246
|
-
const [error, setError] = (0,
|
|
1247
|
-
const fetchFiles = (0,
|
|
1683
|
+
const [files, setFiles] = (0, import_react6.useState)([]);
|
|
1684
|
+
const [isLoading, setIsLoading] = (0, import_react6.useState)(false);
|
|
1685
|
+
const [isUploading, setIsUploading] = (0, import_react6.useState)(false);
|
|
1686
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1687
|
+
const fetchFiles = (0, import_react6.useCallback)(async () => {
|
|
1248
1688
|
setIsLoading(true);
|
|
1249
1689
|
setError(null);
|
|
1250
1690
|
try {
|
|
@@ -1262,7 +1702,7 @@ function useFiles(autoFetch = true) {
|
|
|
1262
1702
|
setIsLoading(false);
|
|
1263
1703
|
}
|
|
1264
1704
|
}, [fetchWithAuth]);
|
|
1265
|
-
const uploadFile = (0,
|
|
1705
|
+
const uploadFile = (0, import_react6.useCallback)(
|
|
1266
1706
|
async (fileOrFormData) => {
|
|
1267
1707
|
setIsUploading(true);
|
|
1268
1708
|
setError(null);
|
|
@@ -1295,7 +1735,7 @@ function useFiles(autoFetch = true) {
|
|
|
1295
1735
|
},
|
|
1296
1736
|
[fetchWithAuth, fetchFiles]
|
|
1297
1737
|
);
|
|
1298
|
-
const deleteFile = (0,
|
|
1738
|
+
const deleteFile = (0, import_react6.useCallback)(
|
|
1299
1739
|
async (fileId) => {
|
|
1300
1740
|
const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
|
|
1301
1741
|
if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
|
|
@@ -1303,7 +1743,7 @@ function useFiles(autoFetch = true) {
|
|
|
1303
1743
|
},
|
|
1304
1744
|
[fetchWithAuth]
|
|
1305
1745
|
);
|
|
1306
|
-
const bulkDeleteFiles = (0,
|
|
1746
|
+
const bulkDeleteFiles = (0, import_react6.useCallback)(
|
|
1307
1747
|
async (fileIds) => {
|
|
1308
1748
|
const res = await fetchWithAuth("/files/bulk-delete", {
|
|
1309
1749
|
method: "POST",
|
|
@@ -1318,10 +1758,10 @@ function useFiles(autoFetch = true) {
|
|
|
1318
1758
|
},
|
|
1319
1759
|
[fetchWithAuth]
|
|
1320
1760
|
);
|
|
1321
|
-
const getDownloadUrl = (0,
|
|
1761
|
+
const getDownloadUrl = (0, import_react6.useCallback)((fileId) => {
|
|
1322
1762
|
return `/files/${fileId}`;
|
|
1323
1763
|
}, []);
|
|
1324
|
-
(0,
|
|
1764
|
+
(0, import_react6.useEffect)(() => {
|
|
1325
1765
|
if (autoFetch) {
|
|
1326
1766
|
void fetchFiles();
|
|
1327
1767
|
}
|
|
@@ -1340,13 +1780,13 @@ function useFiles(autoFetch = true) {
|
|
|
1340
1780
|
}
|
|
1341
1781
|
|
|
1342
1782
|
// src/hooks/useAgents.ts
|
|
1343
|
-
var
|
|
1783
|
+
var import_react7 = require("react");
|
|
1344
1784
|
function useAgents(autoFetch = true) {
|
|
1345
1785
|
const { fetchWithAuth } = usePersonaContext();
|
|
1346
|
-
const [agents, setAgents] = (0,
|
|
1347
|
-
const [isLoading, setIsLoading] = (0,
|
|
1348
|
-
const [error, setError] = (0,
|
|
1349
|
-
const fetchAgents = (0,
|
|
1786
|
+
const [agents, setAgents] = (0, import_react7.useState)([]);
|
|
1787
|
+
const [isLoading, setIsLoading] = (0, import_react7.useState)(false);
|
|
1788
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
1789
|
+
const fetchAgents = (0, import_react7.useCallback)(async () => {
|
|
1350
1790
|
setIsLoading(true);
|
|
1351
1791
|
setError(null);
|
|
1352
1792
|
try {
|
|
@@ -1364,7 +1804,7 @@ function useAgents(autoFetch = true) {
|
|
|
1364
1804
|
setIsLoading(false);
|
|
1365
1805
|
}
|
|
1366
1806
|
}, [fetchWithAuth]);
|
|
1367
|
-
(0,
|
|
1807
|
+
(0, import_react7.useEffect)(() => {
|
|
1368
1808
|
if (autoFetch) {
|
|
1369
1809
|
void fetchAgents();
|
|
1370
1810
|
}
|
|
@@ -1378,13 +1818,13 @@ function useAgents(autoFetch = true) {
|
|
|
1378
1818
|
}
|
|
1379
1819
|
|
|
1380
1820
|
// src/hooks/useConnection.ts
|
|
1381
|
-
var
|
|
1821
|
+
var import_react8 = require("react");
|
|
1382
1822
|
function useConnection(autoCheck = true) {
|
|
1383
1823
|
const { fetchWithAuth } = usePersonaContext();
|
|
1384
|
-
const [health, setHealth] = (0,
|
|
1385
|
-
const [isConnected, setIsConnected] = (0,
|
|
1386
|
-
const [isLoading, setIsLoading] = (0,
|
|
1387
|
-
const checkHealth = (0,
|
|
1824
|
+
const [health, setHealth] = (0, import_react8.useState)(null);
|
|
1825
|
+
const [isConnected, setIsConnected] = (0, import_react8.useState)(false);
|
|
1826
|
+
const [isLoading, setIsLoading] = (0, import_react8.useState)(false);
|
|
1827
|
+
const checkHealth = (0, import_react8.useCallback)(async () => {
|
|
1388
1828
|
setIsLoading(true);
|
|
1389
1829
|
try {
|
|
1390
1830
|
const res = await fetchWithAuth("/health");
|
|
@@ -1403,7 +1843,7 @@ function useConnection(autoCheck = true) {
|
|
|
1403
1843
|
setIsLoading(false);
|
|
1404
1844
|
}
|
|
1405
1845
|
}, [fetchWithAuth]);
|
|
1406
|
-
(0,
|
|
1846
|
+
(0, import_react8.useEffect)(() => {
|
|
1407
1847
|
if (autoCheck) {
|
|
1408
1848
|
void checkHealth();
|
|
1409
1849
|
}
|
|
@@ -1417,15 +1857,15 @@ function useConnection(autoCheck = true) {
|
|
|
1417
1857
|
}
|
|
1418
1858
|
|
|
1419
1859
|
// src/hooks/useMcpConnections.ts
|
|
1420
|
-
var
|
|
1860
|
+
var import_react9 = require("react");
|
|
1421
1861
|
function useMcpConnections(options = {}) {
|
|
1422
1862
|
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
1423
1863
|
const agentId = options.agentId ?? defaultAgentId;
|
|
1424
1864
|
const autoFetch = options.autoFetch ?? true;
|
|
1425
|
-
const [connections, setConnections] = (0,
|
|
1426
|
-
const [isLoading, setIsLoading] = (0,
|
|
1427
|
-
const [error, setError] = (0,
|
|
1428
|
-
const fetchConnections = (0,
|
|
1865
|
+
const [connections, setConnections] = (0, import_react9.useState)([]);
|
|
1866
|
+
const [isLoading, setIsLoading] = (0, import_react9.useState)(false);
|
|
1867
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1868
|
+
const fetchConnections = (0, import_react9.useCallback)(async () => {
|
|
1429
1869
|
if (!agentId) return [];
|
|
1430
1870
|
setIsLoading(true);
|
|
1431
1871
|
setError(null);
|
|
@@ -1449,7 +1889,7 @@ function useMcpConnections(options = {}) {
|
|
|
1449
1889
|
setIsLoading(false);
|
|
1450
1890
|
}
|
|
1451
1891
|
}, [agentId, fetchWithAuth, options.returnTo]);
|
|
1452
|
-
(0,
|
|
1892
|
+
(0, import_react9.useEffect)(() => {
|
|
1453
1893
|
if (autoFetch) void fetchConnections();
|
|
1454
1894
|
}, [autoFetch, fetchConnections]);
|
|
1455
1895
|
return {
|
|
@@ -1464,7 +1904,7 @@ function useMcpConnections(options = {}) {
|
|
|
1464
1904
|
|
|
1465
1905
|
// src/index.ts
|
|
1466
1906
|
var import_logger2 = require("@personaai/logger");
|
|
1467
|
-
var VERSION = "0.
|
|
1907
|
+
var VERSION = "0.7.0";
|
|
1468
1908
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1469
1909
|
0 && (module.exports = {
|
|
1470
1910
|
PersonaProvider,
|
|
@@ -1483,6 +1923,7 @@ var VERSION = "0.5.4";
|
|
|
1483
1923
|
useMcpConnections,
|
|
1484
1924
|
useMemory,
|
|
1485
1925
|
usePersonaContext,
|
|
1486
|
-
useThreads
|
|
1926
|
+
useThreads,
|
|
1927
|
+
useVoice
|
|
1487
1928
|
});
|
|
1488
1929
|
//# sourceMappingURL=index.cjs.map
|