@personaai/react 0.7.3 → 0.7.5

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;
@@ -274,6 +284,13 @@ interface SendMessageOverride {
274
284
  threadId?: string | Promise<string | undefined>;
275
285
  /** Answers/approves a paused interrupt from a previous turn instead of starting a fresh one. */
276
286
  resume?: PersonaResumeValue;
287
+ /**
288
+ * Caller-supplied context (e.g. a live end-user profile snapshot) appended to this turn's
289
+ * system prompt only — never persisted, never visible to later turns. Capped at 4000
290
+ * characters server-side (rejected with a 400, not truncated). Mirrors
291
+ * `@personaai/sdk`'s `SendMessageOptions.contextOverride`.
292
+ */
293
+ contextOverride?: string;
277
294
  }
278
295
  /**
279
296
  * `useVoice`'s call state, driven by `voice_activity` events from the
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;
@@ -274,6 +284,13 @@ interface SendMessageOverride {
274
284
  threadId?: string | Promise<string | undefined>;
275
285
  /** Answers/approves a paused interrupt from a previous turn instead of starting a fresh one. */
276
286
  resume?: PersonaResumeValue;
287
+ /**
288
+ * Caller-supplied context (e.g. a live end-user profile snapshot) appended to this turn's
289
+ * system prompt only — never persisted, never visible to later turns. Capped at 4000
290
+ * characters server-side (rejected with a 400, not truncated). Mirrors
291
+ * `@personaai/sdk`'s `SendMessageOptions.contextOverride`.
292
+ */
293
+ contextOverride?: string;
277
294
  }
278
295
  /**
279
296
  * `useVoice`'s call state, driven by `voice_activity` events from the
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();
@@ -523,12 +595,14 @@ function useChat(options = {}) {
523
595
  agentId: targetAgentId,
524
596
  threadId: threadId ?? overrideOptions?.threadId,
525
597
  hasResume: !!overrideOptions?.resume,
598
+ hasContextOverride: !!overrideOptions?.contextOverride,
526
599
  messageCount: messages.length
527
600
  });
528
601
  chatLogger.trace("sendMessage details", {
529
602
  agentId: targetAgentId,
530
603
  promptPreview: prompt.slice(0, 200),
531
- hasResume: !!overrideOptions?.resume
604
+ hasResume: !!overrideOptions?.resume,
605
+ hasContextOverride: !!overrideOptions?.contextOverride
532
606
  });
533
607
  const userMessage = {
534
608
  id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
@@ -583,7 +657,8 @@ function useChat(options = {}) {
583
657
  agentId: targetAgentId,
584
658
  messages: payloadMessages,
585
659
  threadId: resolvedThreadId,
586
- resume: overrideOptions?.resume
660
+ resume: overrideOptions?.resume,
661
+ contextOverride: overrideOptions?.contextOverride
587
662
  }),
588
663
  signal: controller.signal
589
664
  });