diff --git a/CHANGELOG.md b/CHANGELOG.md index c468f83..815409d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 **IMPORTANT:** This is the last release before `1.0.0`, which will introduce breaking changes. The upcoming major version will add a component registry (via ShadCN) and significantly change the library developer experience. Most primitive components will be removed in `1.0.0`, so please plan your upgrade path accordingly. A migration guide will be provided to assist upgrading. +[0.5.1] +- `PipecatAppBase` updated to use `startAndConnect` pattern of recent clients. +- Added: `handleConnect` now accepts optional `startBot` params in `PipecatAppBase` so callers can supply connection details at call time without recreating the client. + [0.5.0] - Added: `initDevicesOnMount` prop to `PipecatAppBase`, giving developers full control over when device access permissions are requested. diff --git a/package/package.json b/package/package.json index 343769a..4381161 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "@pipecat-ai/voice-ui-kit", - "version": "0.5.0", + "version": "0.5.1-rc.5", "type": "module", "license": "BSD-2-Clause", "repository": { @@ -49,7 +49,7 @@ "@pipecat-ai/client-js": ">=1.4.0", "@pipecat-ai/client-react": ">=1.1.0", "@pipecat-ai/daily-transport": ">=1.2.1", - "@pipecat-ai/small-webrtc-transport": ">=1.3.0", + "@pipecat-ai/small-webrtc-transport": ">=1.7.0", "react": ">=16.8.0 || >=17.0.0 || >=18.0.0", "react-dom": ">=16.8.0 || >=17.0.0 || >=18.0.0", "three": ">=0.140.0" @@ -73,7 +73,7 @@ }, "@pipecat-ai/small-webrtc-transport": { "optional": true, - "version": "^1.3.0" + "version": "^1.7.0" }, "three": { "optional": true @@ -113,7 +113,7 @@ "@pipecat-ai/client-js": "^1.4.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/small-webrtc-transport": "^1.7.0", "@tailwindcss/vite": "^4.1.13", "@types/node": "^22.18.8", "@types/react": "^19.1.16", @@ -140,4 +140,4 @@ "node": ">=18" }, "packageManager": "pnpm@10.14.0" -} +} \ No newline at end of file diff --git a/package/src/components/PipecatAppBase.tsx b/package/src/components/PipecatAppBase.tsx index 03db728..fe16911 100644 --- a/package/src/components/PipecatAppBase.tsx +++ b/package/src/components/PipecatAppBase.tsx @@ -1,10 +1,10 @@ "use client"; +import { ConversationProvider } from "@/components/ConversationProvider"; import { ThemeProvider, type ThemeProviderProps, } from "@/components/ThemeProvider"; -import { ConversationProvider } from "@/components/ConversationProvider"; import { createTransport } from "@/lib/transports"; import { APIRequest, @@ -17,10 +17,7 @@ import { PipecatClientProvider, } from "@pipecat-ai/client-react"; import type { DailyTransportConstructorOptions } from "@pipecat-ai/daily-transport"; -import type { - SmallWebRTCTransport, - SmallWebRTCTransportConstructorOptions, -} from "@pipecat-ai/small-webrtc-transport"; +import type { SmallWebRTCTransportConstructorOptions } from "@pipecat-ai/small-webrtc-transport"; import React, { useCallback, useEffect, useState } from "react"; /** @@ -73,8 +70,11 @@ export interface PipecatBaseProps { export interface PipecatBaseChildProps { /** Pipecat client instance */ client: PipecatClient | null; - /** Function to initiate a connection to the session. Can be sync or async. */ - handleConnect?: () => void | Promise; + /** + * Function to initiate a connection to the session. Can be sync or async. + * Optional params allow supplying startBot data at call time. + */ + handleConnect?: (params?: APIRequest) => void | Promise; /** Function to disconnect from the current session. Can be sync or async. */ handleDisconnect?: () => void | Promise; /** Error message if connection fails */ @@ -85,10 +85,6 @@ export interface PipecatBaseChildProps { transformedStartBotResponse?: TransportConnectionParams | unknown; } -const defaultStartBotResponseTransformer = ( - response: TransportConnectionParams, -) => response; - /** * PipecatAppBase component that provides a configured Pipecat client with audio capabilities. * @@ -100,6 +96,7 @@ const defaultStartBotResponseTransformer = ( * - Automatically disconnects the client when unmounting * - Optionally disables theme provider based on noThemeProvider prop * - Optionally auto-connects to the session on mount based on connectOnMount prop + * - Allows providing startBot params either as a prop or when calling handleConnect * * @param props - Configuration for the audio client including connection params, transport type, and auto-connect behavior * @returns A provider component that wraps children with client context and handlers @@ -146,17 +143,22 @@ const defaultStartBotResponseTransformer = ( * > * * + * + * // Passing startBot params at call time (when they aren't available at mount) + * + * {({ handleConnect }) => ( + * + * )} + * * ``` */ export const PipecatAppBase: React.FC = ({ clientOptions, connectOnMount = false, - connectParams, initDevicesOnMount = false, noAudioOutput = false, noThemeProvider = false, startBotParams, - startBotResponseTransformer = defaultStartBotResponseTransformer, transportOptions, transportType, themeProps, @@ -164,42 +166,16 @@ export const PipecatAppBase: React.FC = ({ }) => { const [client, setClient] = useState(null); const [error, setError] = useState(null); - const [rawStartBotResponse, setRawStartBotResponse] = useState< - TransportConnectionParams | unknown - >(null); - const [transformedStartBotResponse, setTransformedStartBotResponse] = - useState(null); const startAndConnect = useCallback( - async (client: PipecatClient) => { + async (client: PipecatClient, overrideParams?: APIRequest) => { + const params = overrideParams ?? startBotParams; + if (!params) { + return; + } try { - if (startBotParams) { - const response = await client.startBot({ - requestData: {}, - ...startBotParams, - }); - setRawStartBotResponse(response); - if (transportType === "smallwebrtc") { - // Check if response has ICEServers - if ( - typeof response === "object" && - response !== null && - "iceConfig" in response - ) { - const iceConfig = response.iceConfig as { - iceServers: RTCIceServer[]; - }; - (client.transport as SmallWebRTCTransport).iceServers = - iceConfig.iceServers; - } - } - const transformedResponse = - await startBotResponseTransformer(response); - await client.connect(transformedResponse); - setTransformedStartBotResponse(transformedResponse); - } else { - await client.connect(connectParams ?? {}); - } + console.debug("Connecting to session with params:", params); + await client.startBotAndConnect(params); } catch (err) { console.error("Connection error:", err); setError( @@ -207,7 +183,7 @@ export const PipecatAppBase: React.FC = ({ ); } }, - [connectParams, startBotParams, startBotResponseTransformer, transportType], + [startBotParams], ); /** @@ -264,7 +240,7 @@ export const PipecatAppBase: React.FC = ({ * Only allows connection from specific states (initialized, disconnected, error). * Clears any previous errors and handles connection failures. */ - const handleConnect = async () => { + const handleConnect = async (params?: APIRequest) => { if ( !client || !["initialized", "disconnected", "error"].includes(client.state) @@ -273,7 +249,7 @@ export const PipecatAppBase: React.FC = ({ } setError(null); - await startAndConnect(client); + await startAndConnect(client, params); }; /** @@ -300,8 +276,6 @@ export const PipecatAppBase: React.FC = ({ handleConnect, handleDisconnect, error, - rawStartBotResponse, - transformedStartBotResponse, }; // Only create PipecatClientProvider when client is fully initialized diff --git a/package/src/components/elements/SessionInfo.tsx b/package/src/components/elements/SessionInfo.tsx index 9e827d2..1d30a46 100644 --- a/package/src/components/elements/SessionInfo.tsx +++ b/package/src/components/elements/SessionInfo.tsx @@ -1,8 +1,8 @@ import CopyText from "@/components/elements/CopyText"; import DataList from "@/components/elements/DataList"; import { TextDashBlankslate } from "@/index"; -import Daily from "@daily-co/daily-js"; import { usePipecatClient } from "@pipecat-ai/client-react"; +import { useEffect, useState } from "react"; interface Props { noTransportType?: boolean; @@ -22,14 +22,44 @@ export const SessionInfo: React.FC = ({ participantId, }) => { const client = usePipecatClient(); + const [dailyVersion, setDailyVersion] = useState(null); + const transport = client?.transport; + const isDailyTransport = !!transport && "dailyCallClient" in transport; + + useEffect(() => { + let isMounted = true; + + const loadDailyVersion = async () => { + try { + const { default: Daily } = await import("@daily-co/daily-js"); + if (!isMounted) { + return; + } + setDailyVersion(Daily.version()); + } catch { + if (isMounted) { + setDailyVersion(null); + } + } + }; + + if (isDailyTransport) { + loadDailyVersion(); + } else { + setDailyVersion(null); + } + + return () => { + isMounted = false; + }; + }, [isDailyTransport]); let transportTypeName = "Unknown"; - if (client && "dailyCallClient" in client.transport) { - transportTypeName = `Daily (v${Daily.version()})`; + if (isDailyTransport) { + transportTypeName = dailyVersion ? `Daily (v${dailyVersion})` : "Daily"; } else if ( // @ts-expect-error - __proto__ not typed - client?.transport.__proto__.constructor.SERVICE_NAME === - "small-webrtc-transport" + transport?.__proto__.constructor.SERVICE_NAME === "small-webrtc-transport" ) { transportTypeName = "Small WebRTC"; } diff --git a/package/src/components/metrics/Metrics.tsx b/package/src/components/metrics/Metrics.tsx index 58512cf..51eedd6 100644 --- a/package/src/components/metrics/Metrics.tsx +++ b/package/src/components/metrics/Metrics.tsx @@ -1,3 +1,5 @@ +import { useState } from "react"; + import { RTVIEvent } from "@pipecat-ai/client-js"; import { usePipecatClientTransportState, @@ -14,7 +16,6 @@ import { Title, Tooltip, } from "chart.js"; -import { useState } from "react"; import { Line } from "react-chartjs-2"; import { cn } from "@/lib/utils"; diff --git a/package/src/components/panels/ConversationPanel.tsx b/package/src/components/panels/ConversationPanel.tsx index 2818242..a4f1a19 100644 --- a/package/src/components/panels/ConversationPanel.tsx +++ b/package/src/components/panels/ConversationPanel.tsx @@ -1,12 +1,16 @@ import type { ConversationProps } from "@/components/elements/Conversation"; import Conversation from "@/components/elements/Conversation"; -import { Metrics } from "@/components/metrics"; import { Button } from "@/components/ui/button"; import { Panel, PanelContent, PanelHeader } from "@/components/ui/panel"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { TextMode } from "@/types/conversation"; import { LineChartIcon, MessagesSquareIcon } from "lucide-react"; -import { memo, useState } from "react"; +import { Suspense, lazy, memo, useState } from "react"; + +const LazyMetrics = lazy(async () => { + const { Metrics } = await import("@/components/metrics"); + return { default: Metrics }; +}); interface ConversationPanelProps { /** @@ -90,7 +94,15 @@ export const ConversationPanel: React.FC = memo( )} {!noMetrics && ( - + + Loading metrics... + + } + > + + )} diff --git a/package/tsup.config.ts b/package/tsup.config.ts index 01889a6..0b89bd3 100644 --- a/package/tsup.config.ts +++ b/package/tsup.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ }, format: ["esm", "cjs"], dts: true, - splitting: false, + splitting: true, clean: true, external: [ "react", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b06da66..8b86e0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -425,8 +425,8 @@ importers: specifier: ^1.4.0 version: 1.4.0(@pipecat-ai/client-js@1.4.0) '@pipecat-ai/small-webrtc-transport': - specifier: ^1.5.0 - version: 1.5.0(@pipecat-ai/client-js@1.4.0) + specifier: ^1.7.0 + version: 1.7.0(@pipecat-ai/client-js@1.4.0) '@tailwindcss/vite': specifier: ^4.1.13 version: 4.1.13(vite@7.1.7(@types/node@22.18.8)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.5)) @@ -1470,6 +1470,11 @@ packages: peerDependencies: '@pipecat-ai/client-js': ~1.4.0 + '@pipecat-ai/small-webrtc-transport@1.7.0': + resolution: {integrity: sha512-yMBHWla22S3sIfo0kckyj6AJsZms3syRja1IDXc1NOC/v0iNVGlJ5EbeWrup5zJdVc4bc8JQ9kZYI5jQYP219Q==} + peerDependencies: + '@pipecat-ai/client-js': ~1.4.0 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -6977,6 +6982,13 @@ snapshots: dequal: 2.0.3 lodash: 4.17.21 + '@pipecat-ai/small-webrtc-transport@1.7.0(@pipecat-ai/client-js@1.4.0)': + dependencies: + '@daily-co/daily-js': 0.83.1 + '@pipecat-ai/client-js': 1.4.0 + dequal: 2.0.3 + lodash: 4.17.21 + '@pkgjs/parseargs@0.11.0': optional: true