Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"examples/*"
],
"dependencies": {
"@daily-co/daily-js": "^0.84.0",
"@pipecat-ai/client-js": "^1.4.0",
"@daily-co/daily-js": "^0.85.0",
"@pipecat-ai/client-js": "^1.5.0",
"@pipecat-ai/client-react": "^1.1.0",
"react": "^19.2.1",
"react-dom": "^19.2.1",
Expand Down
9 changes: 5 additions & 4 deletions package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,18 @@
"lucide-react": "^0.511.0",
"react-chartjs-2": "^5.3.0",
"react-resizable-panels": "^3.0.6",
"semver": "^7.6.3",
"tailwind-merge": "^3.3.1",
"zustand": "^5.0.8"
},
"devDependencies": {
"@daily-co/daily-js": "^0.80.0",
"@daily-co/daily-js": "^0.85.0",
"@eslint/js": "^9.36.0",
"@ladle/react": "^5.0.3",
"@pipecat-ai/client-js": "^1.4.0",
"@pipecat-ai/client-js": "^1.5.0",
"@pipecat-ai/client-react": "^1.1.0",
"@pipecat-ai/daily-transport": "^1.4.0",
"@pipecat-ai/small-webrtc-transport": "^1.5.0",
"@pipecat-ai/daily-transport": "^1.5.0",
"@pipecat-ai/small-webrtc-transport": "^1.8.0",
"@tailwindcss/vite": "^4.1.13",
"@types/node": "^22.18.8",
"@types/react": "^19.1.16",
Expand Down
52 changes: 30 additions & 22 deletions package/src/components/ConversationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type ConversationMessage,
type ConversationMessagePart,
} from "@/types/conversation";
import { useBotMessages } from "@/hooks/useBotMessages";
import { RTVIEvent } from "@pipecat-ai/client-js";
import { useRTVIClientEvent } from "@pipecat-ai/client-react";
import { createContext, useContext, useRef } from "react";
Expand All @@ -29,7 +30,6 @@ export const ConversationProvider = ({ children }: React.PropsWithChildren) => {
injectMessage,
upsertUserTranscript,
updateAssistantText,
startAssistantLlmStream,
} = useConversationStore();

const userStoppedTimeout = useRef<ReturnType<typeof setTimeout>>(undefined);
Expand All @@ -39,22 +39,8 @@ export const ConversationProvider = ({ children }: React.PropsWithChildren) => {
clearMessages();
});

useRTVIClientEvent(RTVIEvent.BotLlmStarted, () => {
startAssistantLlmStream();
// Nudge a reset counter so any consumer logic can infer fresh turn if needed
assistantStreamResetRef.current += 1;
});

useRTVIClientEvent(RTVIEvent.BotLlmText, (data) => {
updateAssistantText(data.text, false, "llm");
});

useRTVIClientEvent(RTVIEvent.BotLlmStopped, () => {
finalizeLastMessage("assistant");
});

useRTVIClientEvent(RTVIEvent.BotTtsStarted, () => {
// Start a new assistant message for TTS if there isn't one already in progress
// Helper to ensure assistant message exists
const ensureAssistantMessage = () => {
const store = useConversationStore.getState();
const lastAssistantIndex = store.messages.findLastIndex(
(msg: ConversationMessage) => msg.role === "assistant",
Expand All @@ -70,15 +56,37 @@ export const ConversationProvider = ({ children }: React.PropsWithChildren) => {
final: false,
parts: [],
});
assistantStreamResetRef.current += 1;
return true;
}
});
return false;
};

useRTVIClientEvent(RTVIEvent.BotTtsText, (data) => {
updateAssistantText(data.text, false, "tts");
// Use the bot messages hook to handle BotOutput detection and fallback
useBotMessages({
onBotMessageStarted: () => {
ensureAssistantMessage();
},
onBotMessageChunk: (type, text) => {
// The hook handles spacing for BotOutput chunks internally
// For legacy events, spacing is handled by the store for TTS
updateAssistantText(text, false, type);
},
onBotMessageEnded: () => {
const store = useConversationStore.getState();
const lastAssistant = store.messages.findLast(
(m: ConversationMessage) => m.role === "assistant",
);

if (lastAssistant && !lastAssistant.final) {
finalizeLastMessage("assistant");
}
},
});

useRTVIClientEvent(RTVIEvent.BotTtsStopped, () => {
// Finalize the TTS text stream
useRTVIClientEvent(RTVIEvent.BotStoppedSpeaking, () => {
// Finalize the assistant message when bot stops speaking
// This works for both BotOutput and fallback scenarios
const store = useConversationStore.getState();
const lastAssistant = store.messages.findLast(
(m: ConversationMessage) => m.role === "assistant",
Expand Down
44 changes: 25 additions & 19 deletions package/src/components/elements/TranscriptOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";

import { cn } from "@/lib/utils";
import { type BotTTSTextData, RTVIEvent } from "@pipecat-ai/client-js";
import { RTVIEvent } from "@pipecat-ai/client-js";
import {
usePipecatClientTransportState,
useRTVIClientEvent,
} from "@pipecat-ai/client-react";
import { useBotMessages } from "@/hooks/useBotMessages";
import { cva } from "class-variance-authority";
import { useCallback, useState } from "react";

Expand Down Expand Up @@ -183,11 +184,18 @@ export const TranscriptOverlay = ({
const [turnEnd, setIsTurnEnd] = useState(false);
const transportState = usePipecatClientTransportState();

useRTVIClientEvent(
RTVIEvent.BotTtsText,
useCallback(
(event: BotTTSTextData) => {
if (participant === "local") {
// Use the bot messages hook to handle BotOutput detection and fallback
useBotMessages({
onBotMessageChunk: (type, text, metadata) => {
if (participant === "local") {
return;
}

// Only process TTS chunks (spoken content)
if (type === "tts") {
// For BotOutput events, only process word-level chunks
// For legacy events, process all chunks
if (metadata?.aggregated_by && metadata.aggregated_by !== "word") {
return;
}

Expand All @@ -196,24 +204,22 @@ export const TranscriptOverlay = ({
setIsTurnEnd(false);
}

setTranscript((prev) => [...prev, event.text]);
},
[turnEnd, participant],
),
);

useRTVIClientEvent(
RTVIEvent.BotStoppedSpeaking,
useCallback(() => {
setTranscript((prev) => [...prev, text]);
}
},
onBotMessageEnded: (type) => {
if (participant === "local") {
return;
}
setIsTurnEnd(true);
}, [participant]),
);
// Only handle TTS ended events
if (type === "tts") {
setIsTurnEnd(true);
}
},
});

useRTVIClientEvent(
RTVIEvent.BotTtsStopped,
RTVIEvent.BotStoppedSpeaking,
useCallback(() => {
if (participant === "local") {
return;
Expand Down
11 changes: 10 additions & 1 deletion package/src/components/panels/EventsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
PanelTitle,
} from "@/components/ui/panel";
import { cn } from "@/lib/utils";
import { RTVIEvent } from "@pipecat-ai/client-js";
import { BotOutputData, RTVIEvent } from "@pipecat-ai/client-js";

import {
usePipecatClient,
usePipecatClientTransportState,
Expand Down Expand Up @@ -105,6 +106,14 @@ export const EventsPanel: React.FC<Props> = ({ collapsed = false }) => {
});
});

useRTVIClientEvent(RTVIEvent.BotOutput, (data: BotOutputData) => {
addEvent({
event: RTVIEvent.BotOutput,
message: `Bot output (${data.aggregated_by}, spoken: ${data.spoken}): ${data.text}`,
time: new Date().toLocaleTimeString(),
});
});

useRTVIClientEvent(RTVIEvent.Connected, () => {
addEvent({
event: RTVIEvent.Connected,
Expand Down
5 changes: 5 additions & 0 deletions package/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export type {
UsePipecatEventStreamOptions,
} from "./usePipecatEventStream";
export { useTheme } from "./useTheme";
export { useBotMessages } from "./useBotMessages";
export type {
UseBotMessagesCallbacks,
BotMessageChunkMetadata,
} from "./useBotMessages";
Loading