@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/README.md +11 -25
- package/dist/index.cjs +77 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +77 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,35 +109,21 @@ started once a committed user line has intervened.
|
|
|
109
109
|
|
|
110
110
|
### Showing voice turns in a text `useChat` feed
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
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:
|
|
112
|
+
Share the same `threadId` between `useVoice` and `useChat`, and pass the voice hook's return
|
|
113
|
+
value into `useChat`'s `voice` option — that's the whole integration:
|
|
116
114
|
|
|
117
115
|
```tsx
|
|
118
|
-
const
|
|
119
|
-
const { messages,
|
|
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
|
-
};
|
|
116
|
+
const voice = useVoice({ agentId, threadId });
|
|
117
|
+
const { messages, ... } = useChat({ threadId, voice });
|
|
137
118
|
```
|
|
138
119
|
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
`useChat` merges live voice turns into `messages` for you: one bubble per utterance, deduped
|
|
121
|
+
against thread history and against a voice turn that already persisted back into the shared
|
|
122
|
+
thread, consecutive same-speaker fragments folded into the line they opened, and the
|
|
123
|
+
in-progress agent line updated in place while it's still being spoken (`isStreaming: true`, same
|
|
124
|
+
as a text response). Injected messages get a `voice-`-prefixed id. You still call `voice.start()`
|
|
125
|
+
/ `voice.stop()` yourself — `useChat` only owns the transcript-to-feed sync, not the call
|
|
126
|
+
lifecycle.
|
|
141
127
|
|
|
142
128
|
## Devtools
|
|
143
129
|
|
package/dist/index.cjs
CHANGED
|
@@ -435,6 +435,9 @@ function useChat(options = {}) {
|
|
|
435
435
|
const [presentedFile, setPresentedFile] = (0, import_react2.useState)(null);
|
|
436
436
|
const abortControllerRef = (0, import_react2.useRef)(null);
|
|
437
437
|
const loadedThreadIdRef = (0, import_react2.useRef)(void 0);
|
|
438
|
+
const voiceThreadRef = (0, import_react2.useRef)(threadId);
|
|
439
|
+
const voicePrevLenRef = (0, import_react2.useRef)(0);
|
|
440
|
+
const voiceStreamingIdRef = (0, import_react2.useRef)(null);
|
|
438
441
|
const stop = (0, import_react2.useCallback)(() => {
|
|
439
442
|
if (abortControllerRef.current) {
|
|
440
443
|
chatLogger.info("stop streaming", {});
|
|
@@ -540,6 +543,75 @@ function useChat(options = {}) {
|
|
|
540
543
|
chatLogger.debug("loadThreadMessages trigger", { threadId });
|
|
541
544
|
void loadThreadMessages(threadId);
|
|
542
545
|
}, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
546
|
+
const voice = options.voice;
|
|
547
|
+
(0, import_react2.useEffect)(() => {
|
|
548
|
+
if (!voice) return;
|
|
549
|
+
if (voiceThreadRef.current !== threadId) {
|
|
550
|
+
voiceThreadRef.current = threadId;
|
|
551
|
+
voicePrevLenRef.current = voice.transcript.length;
|
|
552
|
+
voiceStreamingIdRef.current = null;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
|
|
556
|
+
const curLen = voice.transcript.length;
|
|
557
|
+
if (curLen < voicePrevLenRef.current) {
|
|
558
|
+
voicePrevLenRef.current = 0;
|
|
559
|
+
voiceStreamingIdRef.current = null;
|
|
560
|
+
}
|
|
561
|
+
if (!isVoiceActive) {
|
|
562
|
+
voicePrevLenRef.current = voice.transcript.length;
|
|
563
|
+
if (voiceStreamingIdRef.current) {
|
|
564
|
+
const doneId = voiceStreamingIdRef.current;
|
|
565
|
+
voiceStreamingIdRef.current = null;
|
|
566
|
+
setMessages(
|
|
567
|
+
(prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (curLen > voicePrevLenRef.current) {
|
|
573
|
+
const newLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
574
|
+
voicePrevLenRef.current = curLen;
|
|
575
|
+
for (const line of newLines) {
|
|
576
|
+
const text = (line.text || "").trim();
|
|
577
|
+
if (!text) continue;
|
|
578
|
+
const role = line.speaker === "user" ? "user" : "assistant";
|
|
579
|
+
const voiceId = `voice-${line.id}`;
|
|
580
|
+
setMessages((prev) => {
|
|
581
|
+
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
582
|
+
if (prev.some(
|
|
583
|
+
(m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text
|
|
584
|
+
))
|
|
585
|
+
return prev;
|
|
586
|
+
const last = prev[prev.length - 1];
|
|
587
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
588
|
+
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
589
|
+
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
590
|
+
}
|
|
591
|
+
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const partialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
|
|
596
|
+
if (partialText) {
|
|
597
|
+
setMessages((prev) => {
|
|
598
|
+
const last = prev[prev.length - 1];
|
|
599
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant") {
|
|
600
|
+
voiceStreamingIdRef.current = last.id;
|
|
601
|
+
return [...prev.slice(0, -1), { ...last, content: partialText, isStreaming: true }];
|
|
602
|
+
}
|
|
603
|
+
const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
|
|
604
|
+
voiceStreamingIdRef.current = id;
|
|
605
|
+
return [...prev, { id, role: "assistant", content: partialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
606
|
+
});
|
|
607
|
+
} else if (voice.state !== "speaking" && voiceStreamingIdRef.current) {
|
|
608
|
+
const doneId = voiceStreamingIdRef.current;
|
|
609
|
+
voiceStreamingIdRef.current = null;
|
|
610
|
+
setMessages(
|
|
611
|
+
(prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}, [voice?.transcript, voice?.partial, voice?.state, threadId]);
|
|
543
615
|
const sendMessage = (0, import_react2.useCallback)(
|
|
544
616
|
async (contentToSend, overrideOptions) => {
|
|
545
617
|
const prompt = (contentToSend ?? input).trim();
|
|
@@ -566,12 +638,14 @@ function useChat(options = {}) {
|
|
|
566
638
|
agentId: targetAgentId,
|
|
567
639
|
threadId: threadId ?? overrideOptions?.threadId,
|
|
568
640
|
hasResume: !!overrideOptions?.resume,
|
|
641
|
+
hasContextOverride: !!overrideOptions?.contextOverride,
|
|
569
642
|
messageCount: messages.length
|
|
570
643
|
});
|
|
571
644
|
chatLogger.trace("sendMessage details", {
|
|
572
645
|
agentId: targetAgentId,
|
|
573
646
|
promptPreview: prompt.slice(0, 200),
|
|
574
|
-
hasResume: !!overrideOptions?.resume
|
|
647
|
+
hasResume: !!overrideOptions?.resume,
|
|
648
|
+
hasContextOverride: !!overrideOptions?.contextOverride
|
|
575
649
|
});
|
|
576
650
|
const userMessage = {
|
|
577
651
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
@@ -626,7 +700,8 @@ function useChat(options = {}) {
|
|
|
626
700
|
agentId: targetAgentId,
|
|
627
701
|
messages: payloadMessages,
|
|
628
702
|
threadId: resolvedThreadId,
|
|
629
|
-
resume: overrideOptions?.resume
|
|
703
|
+
resume: overrideOptions?.resume,
|
|
704
|
+
contextOverride: overrideOptions?.contextOverride
|
|
630
705
|
}),
|
|
631
706
|
signal: controller.signal
|
|
632
707
|
});
|