@personaai/react 0.7.2 → 0.7.3

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
@@ -94,6 +94,51 @@ function VoiceCall() {
94
94
  Requires a browser with `AudioWorklet` support (every current evergreen browser). Needs an HTTPS
95
95
  page in production — browsers refuse an insecure `ws://` connection from an `https:` page.
96
96
 
97
+ ### How `transcript` is shaped
98
+
99
+ `transcript` is **live-only**: it starts empty on every `start()` and grows purely from the
100
+ voice session's events. The server never replays the thread's prior history into it — pass
101
+ `threadId` and the session *persists* new turns to that thread (so `useChat`/`useThreads` can read
102
+ them back on reload), but `transcript` itself always reflects just what was spoken in this call.
103
+
104
+ Gemini Live streams an agent answer as several incremental transcription fragments. The hook merges
105
+ them so one utterance is **one transcript line**, mirroring how the same turns read back from the
106
+ thread (consecutive assistant messages are coalesced on reload). The merge applies across distinct
107
+ spoken segments of one answer too (e.g. split by a mid-answer tool call) — a new agent line is only
108
+ started once a committed user line has intervened.
109
+
110
+ ### Showing voice turns in a text `useChat` feed
111
+
112
+ If you drive a `useVoice({ agentId, threadId })` session alongside `useChat({ threadId })` and
113
+ inject live turns into the chat feed yourself, dedupe before injecting. Because the session
114
+ persists to the same thread, `useChat.messages` may already carry a turn once it refreshes or on
115
+ reload — re-injecting the live copy appends it a second time:
116
+
117
+ ```tsx
118
+ const { transcript, ... } = useVoice({ agentId, threadId });
119
+ const { messages, appendMessage, ... } = useChat({ threadId });
120
+
121
+ // Turn a committed voice line into a chat message once, and only if the feed
122
+ // does not already hold this content (from an earlier live inject OR a reload
123
+ // that pulled the persisted thread).
124
+ const pushVoiceLine = (line: { id: string; speaker: "user" | "agent"; text: string }) => {
125
+ if (messages.some((m) => m.id === line.id)) return; // injected before
126
+ if (messages.some((m) =>
127
+ !String(m.id).startsWith("voice-") &&
128
+ m.role === line.speaker &&
129
+ String(m.content).trim() === line.text.trim(),
130
+ )) return; // already on the thread
131
+ appendMessage({
132
+ id: `voice-${line.id}`,
133
+ role: line.speaker,
134
+ content: line.text,
135
+ });
136
+ };
137
+ ```
138
+
139
+ Voice turns are intentionally **live-only** in `useVoice` — history is the thread's job, not the
140
+ hook's, so when you share a thread with `useChat` you own the dedup at the injection boundary.
141
+
97
142
  ## Devtools
98
143
 
99
144
  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
@@ -1482,10 +1482,11 @@ function useVoice(options = {}) {
1482
1482
  }
1483
1483
  setPartial((prev) => prev?.speaker === speaker ? null : prev);
1484
1484
  const last = lastFinalTurnRef.current;
1485
- const sameUtterance = last && last.speaker === speaker && last.turnSeq === turnSeq;
1485
+ const sameUserBurst = speaker === "user" && last && last.speaker === speaker && last.turnSeq === turnSeq;
1486
1486
  setTranscript((prev) => {
1487
1487
  const tail = prev[prev.length - 1];
1488
- if (sameUtterance && tail?.speaker === speaker) {
1488
+ const agentContinues = speaker === "agent" && tail?.speaker === "agent";
1489
+ if (agentContinues || sameUserBurst && tail?.speaker === "user") {
1489
1490
  const merged = mergeTranscriptText(tail.text, text);
1490
1491
  if (merged === tail.text) return prev;
1491
1492
  return prev.map(
@@ -1925,7 +1926,7 @@ function useMcpConnections(options = {}) {
1925
1926
 
1926
1927
  // src/index.ts
1927
1928
  var import_logger2 = require("@personaai/logger");
1928
- var VERSION = "0.7.2";
1929
+ var VERSION = "0.7.3";
1929
1930
  // Annotate the CommonJS export names for ESM import in node:
1930
1931
  0 && (module.exports = {
1931
1932
  PersonaProvider,