@personaai/react 0.7.0 → 0.7.1

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 CHANGED
@@ -49,12 +49,51 @@ function Chat() {
49
49
  | Hook | Purpose |
50
50
  | --------------- | -------------------------------------------------------------------------- |
51
51
  | `useChat` | Streaming chat — messages, send, stop, reload, interrupts, workspace files |
52
+ | `useVoice` | Real-time voice calls (Gemini Live) — start/stop/mute, live transcript, tool calls |
52
53
  | `useThreads` | Thread CRUD — list, create, delete, rename, reset, archive |
53
54
  | `useFiles` | Upload management — list, upload, delete |
54
55
  | `useMemory` | Persistent memory — read, write, delete |
55
56
  | `useAgents` | Agent discovery — list available agents |
56
57
  | `useConnection` | Health check — backend connectivity status |
57
58
 
59
+ ## Voice
60
+
61
+ Real-time voice calls with an Agent, powered by Gemini Live. Requires your backend to be running
62
+ `@personaai/runtime@^0.7.0` (or a framework adapter on top of it) so `POST /voice/sessions` exists
63
+ to mint tickets — see that package's README for why the actual call bypasses your backend and
64
+ goes straight to Persona instead of relaying through it like `useChat` does.
65
+
66
+ ```tsx
67
+ import { useVoice } from "@personaai/react";
68
+
69
+ function VoiceCall() {
70
+ const { state, transcript, partial, toolCalls, start, stop, mute, isMuted } = useVoice();
71
+
72
+ return (
73
+ <div>
74
+ <p>State: {state}</p>
75
+ {state === "idle" ? (
76
+ <button onClick={start}>Start call</button>
77
+ ) : (
78
+ <>
79
+ <button onClick={() => mute(!isMuted)}>{isMuted ? "Unmute" : "Mute"}</button>
80
+ <button onClick={stop}>End call</button>
81
+ </>
82
+ )}
83
+ {transcript.map((line) => (
84
+ <p key={line.id}>
85
+ <b>{line.speaker}:</b> {line.text}
86
+ </p>
87
+ ))}
88
+ {partial && <p style={{ opacity: 0.6 }}>{partial.text}</p>}
89
+ </div>
90
+ );
91
+ }
92
+ ```
93
+
94
+ Requires a browser with `AudioWorklet` support (every current evergreen browser). Needs an HTTPS
95
+ page in production — browsers refuse an insecure `ws://` connection from an `https:` page.
96
+
58
97
  ## Devtools
59
98
 
60
99
  Floating panel for local debugging — inspect hooks, messages and threads with zero runtime changes. Dev-only, not bundled to production unless imported.
package/dist/index.cjs CHANGED
@@ -1351,6 +1351,7 @@ function useVoice(options = {}) {
1351
1351
  const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
1352
1352
  const voiceLogger = (0, import_react5.useMemo)(() => logger.child("voice"), [logger]);
1353
1353
  const agentId = options.agentId || defaultAgentId;
1354
+ const threadId = options.threadId;
1354
1355
  const [state, setState] = (0, import_react5.useState)("idle");
1355
1356
  const [isMuted, setIsMuted] = (0, import_react5.useState)(false);
1356
1357
  const [transcript, setTranscript] = (0, import_react5.useState)(
@@ -1454,8 +1455,7 @@ function useVoice(options = {}) {
1454
1455
  (id, patch) => {
1455
1456
  setToolCalls((prev) => {
1456
1457
  const idx = prev.findIndex((t) => t.id === id);
1457
- if (idx === -1)
1458
- return [...prev, { id, status: "running", ...patch }];
1458
+ if (idx === -1) return [...prev, { id, status: "running", ...patch }];
1459
1459
  const next = [...prev];
1460
1460
  next[idx] = { ...next[idx], ...patch };
1461
1461
  return next;
@@ -1585,7 +1585,9 @@ function useVoice(options = {}) {
1585
1585
  }
1586
1586
  case "RUN_ERROR":
1587
1587
  voiceLogger.warn("RUN_ERROR", { message: msg.message });
1588
- setError(new Error(msg.message || "The agent run failed."));
1588
+ setError(
1589
+ new Error(msg.message || "The agent run failed.")
1590
+ );
1589
1591
  setState("error");
1590
1592
  break;
1591
1593
  default:
@@ -1610,12 +1612,15 @@ function useVoice(options = {}) {
1610
1612
  setToolCalls([]);
1611
1613
  acceptedTurnSeqRef.current = 0;
1612
1614
  setState("connecting");
1613
- voiceLogger.debug("start() called", { agentId });
1615
+ voiceLogger.debug("start() called", { agentId, threadId });
1614
1616
  try {
1615
1617
  const res = await fetchWithAuth("/voice/sessions", {
1616
1618
  method: "POST",
1617
1619
  headers: { "Content-Type": "application/json" },
1618
- body: JSON.stringify({ agentId })
1620
+ body: JSON.stringify({
1621
+ agentId,
1622
+ ...threadId ? { threadId } : {}
1623
+ })
1619
1624
  });
1620
1625
  if (!res.ok) {
1621
1626
  throw new Error(`Failed to start voice session: ${res.statusText}`);
@@ -1650,7 +1655,7 @@ function useVoice(options = {}) {
1650
1655
  }
1651
1656
  teardownAudio();
1652
1657
  }
1653
- }, [agentId, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
1658
+ }, [agentId, threadId, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
1654
1659
  const mute = (0, import_react5.useCallback)((muted) => {
1655
1660
  setIsMuted(muted);
1656
1661
  streamRef.current?.getAudioTracks().forEach((track) => {