@personaai/react 0.6.0 → 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 +457 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -2
- package/dist/index.d.ts +66 -2
- package/dist/index.js +455 -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
|
|
|
@@ -1252,15 +1253,438 @@ function useThreads(autoFetch = true) {
|
|
|
1252
1253
|
};
|
|
1253
1254
|
}
|
|
1254
1255
|
|
|
1255
|
-
// src/hooks/
|
|
1256
|
+
// src/hooks/useVoice.ts
|
|
1256
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");
|
|
1257
1681
|
function useFiles(autoFetch = true) {
|
|
1258
1682
|
const { fetchWithAuth } = usePersonaContext();
|
|
1259
|
-
const [files, setFiles] = (0,
|
|
1260
|
-
const [isLoading, setIsLoading] = (0,
|
|
1261
|
-
const [isUploading, setIsUploading] = (0,
|
|
1262
|
-
const [error, setError] = (0,
|
|
1263
|
-
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 () => {
|
|
1264
1688
|
setIsLoading(true);
|
|
1265
1689
|
setError(null);
|
|
1266
1690
|
try {
|
|
@@ -1278,7 +1702,7 @@ function useFiles(autoFetch = true) {
|
|
|
1278
1702
|
setIsLoading(false);
|
|
1279
1703
|
}
|
|
1280
1704
|
}, [fetchWithAuth]);
|
|
1281
|
-
const uploadFile = (0,
|
|
1705
|
+
const uploadFile = (0, import_react6.useCallback)(
|
|
1282
1706
|
async (fileOrFormData) => {
|
|
1283
1707
|
setIsUploading(true);
|
|
1284
1708
|
setError(null);
|
|
@@ -1311,7 +1735,7 @@ function useFiles(autoFetch = true) {
|
|
|
1311
1735
|
},
|
|
1312
1736
|
[fetchWithAuth, fetchFiles]
|
|
1313
1737
|
);
|
|
1314
|
-
const deleteFile = (0,
|
|
1738
|
+
const deleteFile = (0, import_react6.useCallback)(
|
|
1315
1739
|
async (fileId) => {
|
|
1316
1740
|
const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
|
|
1317
1741
|
if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
|
|
@@ -1319,7 +1743,7 @@ function useFiles(autoFetch = true) {
|
|
|
1319
1743
|
},
|
|
1320
1744
|
[fetchWithAuth]
|
|
1321
1745
|
);
|
|
1322
|
-
const bulkDeleteFiles = (0,
|
|
1746
|
+
const bulkDeleteFiles = (0, import_react6.useCallback)(
|
|
1323
1747
|
async (fileIds) => {
|
|
1324
1748
|
const res = await fetchWithAuth("/files/bulk-delete", {
|
|
1325
1749
|
method: "POST",
|
|
@@ -1334,10 +1758,10 @@ function useFiles(autoFetch = true) {
|
|
|
1334
1758
|
},
|
|
1335
1759
|
[fetchWithAuth]
|
|
1336
1760
|
);
|
|
1337
|
-
const getDownloadUrl = (0,
|
|
1761
|
+
const getDownloadUrl = (0, import_react6.useCallback)((fileId) => {
|
|
1338
1762
|
return `/files/${fileId}`;
|
|
1339
1763
|
}, []);
|
|
1340
|
-
(0,
|
|
1764
|
+
(0, import_react6.useEffect)(() => {
|
|
1341
1765
|
if (autoFetch) {
|
|
1342
1766
|
void fetchFiles();
|
|
1343
1767
|
}
|
|
@@ -1356,13 +1780,13 @@ function useFiles(autoFetch = true) {
|
|
|
1356
1780
|
}
|
|
1357
1781
|
|
|
1358
1782
|
// src/hooks/useAgents.ts
|
|
1359
|
-
var
|
|
1783
|
+
var import_react7 = require("react");
|
|
1360
1784
|
function useAgents(autoFetch = true) {
|
|
1361
1785
|
const { fetchWithAuth } = usePersonaContext();
|
|
1362
|
-
const [agents, setAgents] = (0,
|
|
1363
|
-
const [isLoading, setIsLoading] = (0,
|
|
1364
|
-
const [error, setError] = (0,
|
|
1365
|
-
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 () => {
|
|
1366
1790
|
setIsLoading(true);
|
|
1367
1791
|
setError(null);
|
|
1368
1792
|
try {
|
|
@@ -1380,7 +1804,7 @@ function useAgents(autoFetch = true) {
|
|
|
1380
1804
|
setIsLoading(false);
|
|
1381
1805
|
}
|
|
1382
1806
|
}, [fetchWithAuth]);
|
|
1383
|
-
(0,
|
|
1807
|
+
(0, import_react7.useEffect)(() => {
|
|
1384
1808
|
if (autoFetch) {
|
|
1385
1809
|
void fetchAgents();
|
|
1386
1810
|
}
|
|
@@ -1394,13 +1818,13 @@ function useAgents(autoFetch = true) {
|
|
|
1394
1818
|
}
|
|
1395
1819
|
|
|
1396
1820
|
// src/hooks/useConnection.ts
|
|
1397
|
-
var
|
|
1821
|
+
var import_react8 = require("react");
|
|
1398
1822
|
function useConnection(autoCheck = true) {
|
|
1399
1823
|
const { fetchWithAuth } = usePersonaContext();
|
|
1400
|
-
const [health, setHealth] = (0,
|
|
1401
|
-
const [isConnected, setIsConnected] = (0,
|
|
1402
|
-
const [isLoading, setIsLoading] = (0,
|
|
1403
|
-
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 () => {
|
|
1404
1828
|
setIsLoading(true);
|
|
1405
1829
|
try {
|
|
1406
1830
|
const res = await fetchWithAuth("/health");
|
|
@@ -1419,7 +1843,7 @@ function useConnection(autoCheck = true) {
|
|
|
1419
1843
|
setIsLoading(false);
|
|
1420
1844
|
}
|
|
1421
1845
|
}, [fetchWithAuth]);
|
|
1422
|
-
(0,
|
|
1846
|
+
(0, import_react8.useEffect)(() => {
|
|
1423
1847
|
if (autoCheck) {
|
|
1424
1848
|
void checkHealth();
|
|
1425
1849
|
}
|
|
@@ -1433,15 +1857,15 @@ function useConnection(autoCheck = true) {
|
|
|
1433
1857
|
}
|
|
1434
1858
|
|
|
1435
1859
|
// src/hooks/useMcpConnections.ts
|
|
1436
|
-
var
|
|
1860
|
+
var import_react9 = require("react");
|
|
1437
1861
|
function useMcpConnections(options = {}) {
|
|
1438
1862
|
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
1439
1863
|
const agentId = options.agentId ?? defaultAgentId;
|
|
1440
1864
|
const autoFetch = options.autoFetch ?? true;
|
|
1441
|
-
const [connections, setConnections] = (0,
|
|
1442
|
-
const [isLoading, setIsLoading] = (0,
|
|
1443
|
-
const [error, setError] = (0,
|
|
1444
|
-
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 () => {
|
|
1445
1869
|
if (!agentId) return [];
|
|
1446
1870
|
setIsLoading(true);
|
|
1447
1871
|
setError(null);
|
|
@@ -1465,7 +1889,7 @@ function useMcpConnections(options = {}) {
|
|
|
1465
1889
|
setIsLoading(false);
|
|
1466
1890
|
}
|
|
1467
1891
|
}, [agentId, fetchWithAuth, options.returnTo]);
|
|
1468
|
-
(0,
|
|
1892
|
+
(0, import_react9.useEffect)(() => {
|
|
1469
1893
|
if (autoFetch) void fetchConnections();
|
|
1470
1894
|
}, [autoFetch, fetchConnections]);
|
|
1471
1895
|
return {
|
|
@@ -1480,7 +1904,7 @@ function useMcpConnections(options = {}) {
|
|
|
1480
1904
|
|
|
1481
1905
|
// src/index.ts
|
|
1482
1906
|
var import_logger2 = require("@personaai/logger");
|
|
1483
|
-
var VERSION = "0.
|
|
1907
|
+
var VERSION = "0.7.0";
|
|
1484
1908
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1485
1909
|
0 && (module.exports = {
|
|
1486
1910
|
PersonaProvider,
|
|
@@ -1499,6 +1923,7 @@ var VERSION = "0.6.0";
|
|
|
1499
1923
|
useMcpConnections,
|
|
1500
1924
|
useMemory,
|
|
1501
1925
|
usePersonaContext,
|
|
1502
|
-
useThreads
|
|
1926
|
+
useThreads,
|
|
1927
|
+
useVoice
|
|
1503
1928
|
});
|
|
1504
1929
|
//# sourceMappingURL=index.cjs.map
|