@personaai/react 0.7.2 → 0.7.4

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/dist/index.d.cts CHANGED
@@ -258,6 +258,16 @@ interface UseChatOptions {
258
258
  onError?: (error: Error) => void;
259
259
  /** Hook for receiving every low-level AG-UI streaming event (tool calls, steps, subagents) */
260
260
  onEvent?: (event: PersonaStreamingEvent) => void;
261
+ /**
262
+ * Pass the object returned by `useVoice()` (sharing the same `threadId`) to have `useChat`
263
+ * merge live voice turns into `messages` automatically — one bubble per utterance, deduped
264
+ * against thread history and against a voice turn that already persisted back into this
265
+ * thread, with the in-progress agent line updated in place while it's still being spoken.
266
+ * No manual sync effects needed in the host app; injected messages get a `voice-` prefixed id.
267
+ * Only applied while the passed hook's `state` is active (not `idle`/`ended`/`error`); the
268
+ * host app still owns starting/stopping the call itself (e.g. `voice.start()`/`voice.stop()`).
269
+ */
270
+ voice?: UseVoiceResult;
261
271
  }
262
272
  interface SendMessageOverride {
263
273
  agentId?: string;
@@ -574,6 +584,6 @@ declare function supportsStreamingFetch(): boolean;
574
584
  */
575
585
  declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
576
586
 
577
- declare const VERSION = "0.7.2";
587
+ declare const VERSION = "0.7.3";
578
588
 
579
589
  export { type OpenSSEOptions, type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaVoiceEndReason, type PersonaVoiceState, type PersonaVoiceToolCall, type PersonaVoiceTranscriptLine, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, type UseVoiceOptions, type UseVoiceResult, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads, useVoice };
package/dist/index.d.ts CHANGED
@@ -258,6 +258,16 @@ interface UseChatOptions {
258
258
  onError?: (error: Error) => void;
259
259
  /** Hook for receiving every low-level AG-UI streaming event (tool calls, steps, subagents) */
260
260
  onEvent?: (event: PersonaStreamingEvent) => void;
261
+ /**
262
+ * Pass the object returned by `useVoice()` (sharing the same `threadId`) to have `useChat`
263
+ * merge live voice turns into `messages` automatically — one bubble per utterance, deduped
264
+ * against thread history and against a voice turn that already persisted back into this
265
+ * thread, with the in-progress agent line updated in place while it's still being spoken.
266
+ * No manual sync effects needed in the host app; injected messages get a `voice-` prefixed id.
267
+ * Only applied while the passed hook's `state` is active (not `idle`/`ended`/`error`); the
268
+ * host app still owns starting/stopping the call itself (e.g. `voice.start()`/`voice.stop()`).
269
+ */
270
+ voice?: UseVoiceResult;
261
271
  }
262
272
  interface SendMessageOverride {
263
273
  agentId?: string;
@@ -574,6 +584,6 @@ declare function supportsStreamingFetch(): boolean;
574
584
  */
575
585
  declare function openSSEStream(opts: OpenSSEOptions): Promise<SSEStream>;
576
586
 
577
- declare const VERSION = "0.7.2";
587
+ declare const VERSION = "0.7.3";
578
588
 
579
589
  export { type OpenSSEOptions, type PersonaAgentSummary, type PersonaClarificationQuestion, type PersonaFileItem, type PersonaHealthInfo, type PersonaHitlActionRequest, type PersonaInterrupt, type PersonaMcpConnection, type PersonaMemoryAgentGroup, type PersonaMemoryFile, type PersonaMemoryList, type PersonaMessage, type PersonaPresentedFile, PersonaProvider, type PersonaProviderProps, type PersonaResumeValue, type PersonaRole, type PersonaStreamingEvent, type PersonaSubagentActivityEntry, type PersonaThread, type PersonaTodo, type PersonaToolCall, type PersonaVoiceEndReason, type PersonaVoiceState, type PersonaVoiceToolCall, type PersonaVoiceTranscriptLine, type PersonaWorkspaceFile, type SSEReader, type SSEStream, type SendMessageOverride, type UseChatOptions, type UseMcpConnectionsOptions, type UseVoiceOptions, type UseVoiceResult, VERSION, openSSEStream, supportsStreamingFetch, useAgents, useChat, useConnection, useFiles, useMcpConnections, useMemory, usePersonaContext, useThreads, useVoice };
package/dist/index.js CHANGED
@@ -392,6 +392,9 @@ function useChat(options = {}) {
392
392
  const [presentedFile, setPresentedFile] = useState(null);
393
393
  const abortControllerRef = useRef(null);
394
394
  const loadedThreadIdRef = useRef(void 0);
395
+ const voiceThreadRef = useRef(threadId);
396
+ const voicePrevLenRef = useRef(0);
397
+ const voiceStreamingIdRef = useRef(null);
395
398
  const stop = useCallback(() => {
396
399
  if (abortControllerRef.current) {
397
400
  chatLogger.info("stop streaming", {});
@@ -497,6 +500,75 @@ function useChat(options = {}) {
497
500
  chatLogger.debug("loadThreadMessages trigger", { threadId });
498
501
  void loadThreadMessages(threadId);
499
502
  }, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
503
+ const voice = options.voice;
504
+ useEffect(() => {
505
+ if (!voice) return;
506
+ if (voiceThreadRef.current !== threadId) {
507
+ voiceThreadRef.current = threadId;
508
+ voicePrevLenRef.current = voice.transcript.length;
509
+ voiceStreamingIdRef.current = null;
510
+ return;
511
+ }
512
+ const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
513
+ const curLen = voice.transcript.length;
514
+ if (curLen < voicePrevLenRef.current) {
515
+ voicePrevLenRef.current = 0;
516
+ voiceStreamingIdRef.current = null;
517
+ }
518
+ if (!isVoiceActive) {
519
+ voicePrevLenRef.current = voice.transcript.length;
520
+ if (voiceStreamingIdRef.current) {
521
+ const doneId = voiceStreamingIdRef.current;
522
+ voiceStreamingIdRef.current = null;
523
+ setMessages(
524
+ (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
525
+ );
526
+ }
527
+ return;
528
+ }
529
+ if (curLen > voicePrevLenRef.current) {
530
+ const newLines = voice.transcript.slice(voicePrevLenRef.current);
531
+ voicePrevLenRef.current = curLen;
532
+ for (const line of newLines) {
533
+ const text = (line.text || "").trim();
534
+ if (!text) continue;
535
+ const role = line.speaker === "user" ? "user" : "assistant";
536
+ const voiceId = `voice-${line.id}`;
537
+ setMessages((prev) => {
538
+ if (prev.some((m) => m.id === voiceId)) return prev;
539
+ if (prev.some(
540
+ (m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text
541
+ ))
542
+ return prev;
543
+ const last = prev[prev.length - 1];
544
+ if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
545
+ const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
546
+ return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
547
+ }
548
+ return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
549
+ });
550
+ }
551
+ }
552
+ const partialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
553
+ if (partialText) {
554
+ setMessages((prev) => {
555
+ const last = prev[prev.length - 1];
556
+ if (last?.id.startsWith("voice-") && last.role === "assistant") {
557
+ voiceStreamingIdRef.current = last.id;
558
+ return [...prev.slice(0, -1), { ...last, content: partialText, isStreaming: true }];
559
+ }
560
+ const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
561
+ voiceStreamingIdRef.current = id;
562
+ return [...prev, { id, role: "assistant", content: partialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
563
+ });
564
+ } else if (voice.state !== "speaking" && voiceStreamingIdRef.current) {
565
+ const doneId = voiceStreamingIdRef.current;
566
+ voiceStreamingIdRef.current = null;
567
+ setMessages(
568
+ (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
569
+ );
570
+ }
571
+ }, [voice?.transcript, voice?.partial, voice?.state, threadId]);
500
572
  const sendMessage = useCallback(
501
573
  async (contentToSend, overrideOptions) => {
502
574
  const prompt = (contentToSend ?? input).trim();
@@ -1439,10 +1511,11 @@ function useVoice(options = {}) {
1439
1511
  }
1440
1512
  setPartial((prev) => prev?.speaker === speaker ? null : prev);
1441
1513
  const last = lastFinalTurnRef.current;
1442
- const sameUtterance = last && last.speaker === speaker && last.turnSeq === turnSeq;
1514
+ const sameUserBurst = speaker === "user" && last && last.speaker === speaker && last.turnSeq === turnSeq;
1443
1515
  setTranscript((prev) => {
1444
1516
  const tail = prev[prev.length - 1];
1445
- if (sameUtterance && tail?.speaker === speaker) {
1517
+ const agentContinues = speaker === "agent" && tail?.speaker === "agent";
1518
+ if (agentContinues || sameUserBurst && tail?.speaker === "user") {
1446
1519
  const merged = mergeTranscriptText(tail.text, text);
1447
1520
  if (merged === tail.text) return prev;
1448
1521
  return prev.map(
@@ -1888,7 +1961,7 @@ import {
1888
1961
  getLogLevel,
1889
1962
  isLevelEnabled
1890
1963
  } from "@personaai/logger";
1891
- var VERSION = "0.7.2";
1964
+ var VERSION = "0.7.3";
1892
1965
  export {
1893
1966
  PersonaProvider,
1894
1967
  VERSION,