From 88c540f9bcad1da74491ea041c6fa57102ce3889 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 10:14:38 -0300 Subject: [PATCH 1/7] Starting to create the example signalling through app message. --- .../client/javascript/README.md | 27 + .../client/javascript/index.html | 40 + .../client/javascript/package-lock.json | 1082 +++++++++++++++++ .../client/javascript/package.json | 20 + .../client/javascript/src/app.js | 179 +++ .../client/javascript/src/style.css | 98 ++ .../bot-ready-signalling/server/runner.py | 63 + .../server/say-when-bot-ready.py | 60 + 8 files changed, 1569 insertions(+) create mode 100644 examples/bot-ready-signalling/client/javascript/README.md create mode 100644 examples/bot-ready-signalling/client/javascript/index.html create mode 100644 examples/bot-ready-signalling/client/javascript/package-lock.json create mode 100644 examples/bot-ready-signalling/client/javascript/package.json create mode 100644 examples/bot-ready-signalling/client/javascript/src/app.js create mode 100644 examples/bot-ready-signalling/client/javascript/src/style.css create mode 100644 examples/bot-ready-signalling/server/runner.py create mode 100644 examples/bot-ready-signalling/server/say-when-bot-ready.py diff --git a/examples/bot-ready-signalling/client/javascript/README.md b/examples/bot-ready-signalling/client/javascript/README.md new file mode 100644 index 000000000..3b0c7f96e --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/README.md @@ -0,0 +1,27 @@ +# JavaScript Implementation + +Basic implementation using the [Pipecat JavaScript SDK](https://docs.pipecat.ai/client/js/introduction). + +## Setup + +1. Run the bot server. See the [server README](../../README). + +2. Navigate to the `client/javascript` directory: + +```bash +cd client/javascript +``` + +3. Install dependencies: + +```bash +npm install +``` + +4. Run the client app: + +``` +npm run dev +``` + +5. Visit http://localhost:5173 in your browser. diff --git a/examples/bot-ready-signalling/client/javascript/index.html b/examples/bot-ready-signalling/client/javascript/index.html new file mode 100644 index 000000000..d6f4bfcb1 --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/index.html @@ -0,0 +1,40 @@ + + + + + + + AI Chatbot + + + +
+
+
+ Status: Disconnected +
+
+ + +
+
+ +
+
+
+
+ +
+
+ +
+

Debug Info

+
+
+
+ + + + + + \ No newline at end of file diff --git a/examples/bot-ready-signalling/client/javascript/package-lock.json b/examples/bot-ready-signalling/client/javascript/package-lock.json new file mode 100644 index 000000000..f10bc8528 --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/package-lock.json @@ -0,0 +1,1082 @@ +{ + "name": "client", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@daily-co/daily-js": "0.74.0" + }, + "devDependencies": { + "vite": "^6.0.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@daily-co/daily-js": { + "version": "0.74.0", + "resolved": "https://registry.npmjs.org/@daily-co/daily-js/-/daily-js-0.74.0.tgz", + "integrity": "sha512-wyZzt+sH7yh5Cg3DQeoZ1Yen8+bbai0/Zq8JZzklqELrpjiFuu34e3qwjwt79cx7T9eukbZbK6gtk3cmCBW3iw==", + "license": "BSD-2-Clause", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@sentry/browser": "^8.33.1", + "bowser": "^2.8.1", + "dequal": "^2.0.3", + "events": "^3.1.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz", + "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz", + "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz", + "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz", + "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz", + "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz", + "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz", + "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz", + "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz", + "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz", + "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz", + "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz", + "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz", + "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz", + "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz", + "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz", + "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz", + "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz", + "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz", + "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sentry-internal/browser-utils": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.50.0.tgz", + "integrity": "sha512-hZm6ngWTEzZhaMHpLIKB4wWp0Od1MdCZdvR5FRdIThUMLa1P8rXeolovTRfOE81NE755EiwJHzj4O7rq3EjA+A==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.50.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.50.0.tgz", + "integrity": "sha512-79WlvSJYCXL/D0PBC8AIT4JbyS44AE3h6lP05IESXMqzTZl3KeSqCx317rwJw1KaxzeFd/JQwkFq95jaKAcLhg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.50.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.50.0.tgz", + "integrity": "sha512-mhRPujzO6n+mb6ZR+wQNkSpjqIqDriR0hZEvdzHQdyXu9zVdCHUJ3sINkzpT1XwiypQVCEfxB6Oh9y/NmcQfGg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.50.0", + "@sentry/core": "8.50.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.50.0.tgz", + "integrity": "sha512-Hv1bBaPpe62xFPLpuaUxVBUHd/Ed9bnGndeqN4hueeEGDT9T6NyVokgm35O5xE9/op6Yodm/3NfUkEg8oE++Aw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "8.50.0", + "@sentry/core": "8.50.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/browser": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.50.0.tgz", + "integrity": "sha512-aGJSpuKiHVKkLvd1VklJSZ2oCsl4wcKUVxKIa8dhJC8KjDY0vREQCywrlWuS5KYP0xFy4k28pg6PPR3HKkUlNw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.50.0", + "@sentry-internal/feedback": "8.50.0", + "@sentry-internal/replay": "8.50.0", + "@sentry-internal/replay-canvas": "8.50.0", + "@sentry/core": "8.50.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/core": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.50.0.tgz", + "integrity": "sha512-q71m8Ha9YGwqn4Gd7sWvcFTRgbHXxEfU4QeIFtwMBpwHfq2Q+9koiF8DOoOHqIEOsnlvZWRQgGggIOdHzajnVw==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.30.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz", + "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.30.1", + "@rollup/rollup-android-arm64": "4.30.1", + "@rollup/rollup-darwin-arm64": "4.30.1", + "@rollup/rollup-darwin-x64": "4.30.1", + "@rollup/rollup-freebsd-arm64": "4.30.1", + "@rollup/rollup-freebsd-x64": "4.30.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.30.1", + "@rollup/rollup-linux-arm-musleabihf": "4.30.1", + "@rollup/rollup-linux-arm64-gnu": "4.30.1", + "@rollup/rollup-linux-arm64-musl": "4.30.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.30.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1", + "@rollup/rollup-linux-riscv64-gnu": "4.30.1", + "@rollup/rollup-linux-s390x-gnu": "4.30.1", + "@rollup/rollup-linux-x64-gnu": "4.30.1", + "@rollup/rollup-linux-x64-musl": "4.30.1", + "@rollup/rollup-win32-arm64-msvc": "4.30.1", + "@rollup/rollup-win32-ia32-msvc": "4.30.1", + "@rollup/rollup-win32-x64-msvc": "4.30.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", + "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.4.49", + "rollup": "^4.23.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/examples/bot-ready-signalling/client/javascript/package.json b/examples/bot-ready-signalling/client/javascript/package.json new file mode 100644 index 000000000..53b28499b --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/package.json @@ -0,0 +1,20 @@ +{ + "name": "client", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "vite": "^6.0.2" + }, + "dependencies": { + "@daily-co/daily-js": "0.74.0" + } +} diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js new file mode 100644 index 000000000..3e306f656 --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2024–2025, Daily + * + * SPDX-License-Identifier: BSD 2-Clause License + */ + +import Daily from "@daily-co/daily-js"; + +/** + * ChatbotClient handles the connection and media management for a real-time + * voice interaction with an AI bot. + */ +class ChatbotClient { + constructor() { + // Initialize client state + this.dailyCallObject = null; + this.setupDOMElements(); + this.setupEventListeners(); + } + + /** + * Set up references to DOM elements and create necessary media elements + */ + setupDOMElements() { + // Get references to UI control elements + this.connectBtn = document.getElementById('connect-btn'); + this.disconnectBtn = document.getElementById('disconnect-btn'); + this.statusSpan = document.getElementById('connection-status'); + this.debugLog = document.getElementById('debug-log'); + + // Create an audio element for bot's voice output + this.botAudio = document.createElement('audio'); + this.botAudio.autoplay = true; + this.botAudio.playsInline = true; + document.body.appendChild(this.botAudio); + } + + /** + * Set up event listeners for connect/disconnect buttons + */ + setupEventListeners() { + this.connectBtn.addEventListener('click', () => this.connect()); + this.disconnectBtn.addEventListener('click', () => this.disconnect()); + } + + /** + * Add a timestamped message to the debug log + */ + log(message) { + const entry = document.createElement('div'); + entry.textContent = `${new Date().toISOString()} - ${message}`; + + // Add styling based on message type + if (message.startsWith('User: ')) { + entry.style.color = '#2196F3'; // blue for user + } else if (message.startsWith('Bot: ')) { + entry.style.color = '#4CAF50'; // green for bot + } + + this.debugLog.appendChild(entry); + this.debugLog.scrollTop = this.debugLog.scrollHeight; + console.log(message); + } + + /** + * Update the connection status display + */ + updateStatus(status) { + this.statusSpan.textContent = status; + this.log(`Status: ${status}`); + } + + handleEventToConsole (evt) { + console.log("Received event: ", evt); + }; + + /** + * Set up listeners for track events (start/stop) + * This handles new tracks being added during the session + */ + setupTrackListeners() { + if (!this.dailyCallObject) return; + + this.dailyCallObject.on("joined-meeting", this.handleEventToConsole); + this.dailyCallObject.on("track-started", (evt) => { + if (evt.track.kind === "audio" && evt.participant.local === false) { + this.setupAudioTrack(evt.track); + } + }); + this.dailyCallObject.on("track-stopped", this.handleEventToConsole); + this.dailyCallObject.on("participant-joined", this.handleEventToConsole); + this.dailyCallObject.on("participant-updated", this.handleEventToConsole); + this.dailyCallObject.on("participant-left", this.handleEventToConsole); + this.dailyCallObject.on("left-meeting", this.handleEventToConsole); + this.dailyCallObject.on("error", this.handleEventToConsole); + } + + /** + * Set up an audio track for playback + * Handles both initial setup and track updates + */ + setupAudioTrack(track) { + this.log('Setting up audio track'); + // Check if we're already playing this track + if (this.botAudio.srcObject) { + const oldTrack = this.botAudio.srcObject.getAudioTracks()[0]; + if (oldTrack?.id === track.id) return; + } + // Create a new MediaStream with the track and set it as the audio source + this.botAudio.srcObject = new MediaStream([track]); + } + + /** + * Initialize and connect to the bot + * This sets up the RTVI client, initializes devices, and establishes the connection + */ + async connect() { + try { + // Initialize the client + this.dailyCallObject = Daily.createCallObject({ + subscribeToTracksAutomatically: true, + }); + + // Set up listeners for media track events + this.setupTrackListeners(); + + // Connect to the bot + this.log('Connecting to bot...'); + // Only for making debugger easier + window.callObject = this.dailyCallObject; + await this.dailyCallObject.join({ + //TODO fixme, use the URL that we are going to receive from bot + url: "https://filipi.daily.co/public", + }); + + this.log('Connection complete'); + } catch (error) { + // Handle any errors during connection + this.log(`Error connecting: ${error.message}`); + this.log(`Error stack: ${error.stack}`); + this.updateStatus('Error'); + + // Clean up if there's an error + if (this.dailyCallObject) { + try { + await this.dailyCallObject.leave(); + } catch (disconnectError) { + this.log(`Error during disconnect: ${disconnectError.message}`); + } + } + } + } + + /** + * Disconnect from the bot and clean up media resources + */ + async disconnect() { + if (this.dailyCallObject) { + try { + // Disconnect the RTVI client + await this.dailyCallObject.leave(); + this.dailyCallObject = null; + + // Clean up audio + if (this.botAudio.srcObject) { + this.botAudio.srcObject.getTracks().forEach((track) => track.stop()); + this.botAudio.srcObject = null; + } + } catch (error) { + this.log(`Error disconnecting: ${error.message}`); + } + } + } +} + +// Initialize the client when the page loads +window.addEventListener('DOMContentLoaded', () => { + new ChatbotClient(); +}); diff --git a/examples/bot-ready-signalling/client/javascript/src/style.css b/examples/bot-ready-signalling/client/javascript/src/style.css new file mode 100644 index 000000000..a3cb55776 --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/src/style.css @@ -0,0 +1,98 @@ +body { + margin: 0; + padding: 20px; + font-family: Arial, sans-serif; + background-color: #f0f0f0; +} + +.container { + max-width: 1200px; + margin: 0 auto; +} + +.status-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: #fff; + border-radius: 8px; + margin-bottom: 20px; +} + +.controls button { + padding: 8px 16px; + margin-left: 10px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +#connect-btn { + background-color: #4caf50; + color: white; +} + +#disconnect-btn { + background-color: #f44336; + color: white; +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.main-content { + background-color: #fff; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; +} + +.bot-container { + display: flex; + flex-direction: column; + align-items: center; +} + +#bot-video-container { + width: 640px; + height: 360px; + background-color: #e0e0e0; + border-radius: 8px; + margin: 20px auto; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +#bot-video-container video { + width: 100%; + height: 100%; + object-fit: cover; +} + +.debug-panel { + background-color: #fff; + border-radius: 8px; + padding: 20px; +} + +.debug-panel h3 { + margin: 0 0 10px 0; + font-size: 16px; + font-weight: bold; +} + +#debug-log { + height: 200px; + overflow-y: auto; + background-color: #f8f8f8; + padding: 10px; + border-radius: 4px; + font-family: monospace; + font-size: 12px; + line-height: 1.4; +} diff --git a/examples/bot-ready-signalling/server/runner.py b/examples/bot-ready-signalling/server/runner.py new file mode 100644 index 000000000..c1556e8a4 --- /dev/null +++ b/examples/bot-ready-signalling/server/runner.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2024–2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +import argparse +import os + +import aiohttp + +from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper + + +async def configure(aiohttp_session: aiohttp.ClientSession): + (url, token, _) = await configure_with_args(aiohttp_session) + return (url, token) + + +async def configure_with_args( + aiohttp_session: aiohttp.ClientSession, parser: argparse.ArgumentParser | None = None +): + if not parser: + parser = argparse.ArgumentParser(description="Daily AI SDK Bot Sample") + parser.add_argument( + "-u", "--url", type=str, required=False, help="URL of the Daily room to join" + ) + parser.add_argument( + "-k", + "--apikey", + type=str, + required=False, + help="Daily API Key (needed to create an owner token for the room)", + ) + + args, unknown = parser.parse_known_args() + + url = args.url or os.getenv("DAILY_SAMPLE_ROOM_URL") + key = args.apikey or os.getenv("DAILY_API_KEY") + + if not url: + raise Exception( + "No Daily room specified. use the -u/--url option from the command line, or set DAILY_SAMPLE_ROOM_URL in your environment to specify a Daily room URL." + ) + + if not key: + raise Exception( + "No Daily API key specified. use the -k/--apikey option from the command line, or set DAILY_API_KEY in your environment to specify a Daily API key, available from https://dashboard.daily.co/developers." + ) + + daily_rest_helper = DailyRESTHelper( + daily_api_key=key, + daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"), + aiohttp_session=aiohttp_session, + ) + + # Create a meeting token for the given room with an expiration 1 hour in + # the future. + expiry_time: float = 60 * 60 + + token = await daily_rest_helper.get_token(url, expiry_time) + + return (url, token, args) diff --git a/examples/bot-ready-signalling/server/say-when-bot-ready.py b/examples/bot-ready-signalling/server/say-when-bot-ready.py new file mode 100644 index 000000000..e56113ef5 --- /dev/null +++ b/examples/bot-ready-signalling/server/say-when-bot-ready.py @@ -0,0 +1,60 @@ +# +# Copyright (c) 2024–2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +import asyncio +import os +import sys + +import aiohttp +from dotenv import load_dotenv +from loguru import logger +from runner import configure + +from pipecat.frames.frames import EndFrame, TTSSpeakFrame +from pipecat.pipeline.pipeline import Pipeline +from pipecat.pipeline.runner import PipelineRunner +from pipecat.pipeline.task import PipelineTask +from pipecat.services.cartesia import CartesiaTTSService +from pipecat.transports.services.daily import DailyParams, DailyTransport + +load_dotenv(override=True) + +logger.remove(0) +logger.add(sys.stderr, level="DEBUG") + + +async def main(): + async with aiohttp.ClientSession() as session: + (room_url, _) = await configure(session) + + transport = DailyTransport( + room_url, None, "Say One Thing", DailyParams(audio_out_enabled=True) + ) + + tts = CartesiaTTSService( + api_key=os.getenv("CARTESIA_API_KEY"), + voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady + ) + + runner = PipelineRunner() + + task = PipelineTask(Pipeline([tts, transport.output()])) + + # Register an event handler so we can play the audio when we receive a specific message + @transport.event_handler("on_app_message") + async def on_app_message(transport, message, sender): + logger.debug(f"Received app message: {message} - {sender}") + if "message" not in message: + return + await task.queue_frames( + [TTSSpeakFrame(f"Hello there, how are you doing today ?"), EndFrame()] + ) + + await runner.run(task) + + +if __name__ == "__main__": + asyncio.run(main()) From 5dc7d2a378d2760d940e4605fd2db28e661a828f Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 10:28:39 -0300 Subject: [PATCH 2/7] Creating the bot when pressing to connect. --- .../client/javascript/index.html | 10 +- .../client/javascript/src/app.js | 20 ++- .../bot-ready-signalling/server/server.py | 147 ++++++++++++++++++ ...ay-when-bot-ready.py => signalling_bot.py} | 0 4 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 examples/bot-ready-signalling/server/server.py rename examples/bot-ready-signalling/server/{say-when-bot-ready.py => signalling_bot.py} (100%) diff --git a/examples/bot-ready-signalling/client/javascript/index.html b/examples/bot-ready-signalling/client/javascript/index.html index d6f4bfcb1..71dfe4597 100644 --- a/examples/bot-ready-signalling/client/javascript/index.html +++ b/examples/bot-ready-signalling/client/javascript/index.html @@ -19,13 +19,7 @@ -
-
-
-
- -
-
+

Debug Info

@@ -37,4 +31,4 @@

Debug Info

- \ No newline at end of file + diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js index 3e306f656..ea7e26164 100644 --- a/examples/bot-ready-signalling/client/javascript/src/app.js +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -110,6 +110,20 @@ class ChatbotClient { this.botAudio.srcObject = new MediaStream([track]); } + async fetchRoomInfo() { + let connectUrl = 'http://0.0.0.0:7860/connect' + let res = await fetch(connectUrl, { + method: "POST", + mode: "cors", + headers: new Headers({ + "Content-Type": "application/json" + }), + }) + if (res.ok) { + return res.json(); + } + } + /** * Initialize and connect to the bot * This sets up the RTVI client, initializes devices, and establishes the connection @@ -124,13 +138,15 @@ class ChatbotClient { // Set up listeners for media track events this.setupTrackListeners(); + this.log('Creating the bot...'); + let roomInfo = await this.fetchRoomInfo() + // Connect to the bot this.log('Connecting to bot...'); // Only for making debugger easier window.callObject = this.dailyCallObject; await this.dailyCallObject.join({ - //TODO fixme, use the URL that we are going to receive from bot - url: "https://filipi.daily.co/public", + url: roomInfo.room_url, }); this.log('Connection complete'); diff --git a/examples/bot-ready-signalling/server/server.py b/examples/bot-ready-signalling/server/server.py new file mode 100644 index 000000000..ca42aa0fc --- /dev/null +++ b/examples/bot-ready-signalling/server/server.py @@ -0,0 +1,147 @@ +# +# Copyright (c) 2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +import argparse +import os +import subprocess +from contextlib import asynccontextmanager +from typing import Any, Dict + +import aiohttp +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse + +from pipecat.transports.services.helpers.daily_rest import DailyRESTHelper, DailyRoomParams + +# Load environment variables from .env file +load_dotenv(override=True) + +# Dictionary to track bot processes: {pid: (process, room_url)} +bot_procs = {} + +# Store Daily API helpers +daily_helpers = {} + + +def cleanup(): + """Cleanup function to terminate all bot processes. + + Called during server shutdown. + """ + for entry in bot_procs.values(): + proc = entry[0] + proc.terminate() + proc.wait() + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """FastAPI lifespan manager that handles startup and shutdown tasks. + + - Creates aiohttp session + - Initializes Daily API helper + - Cleans up resources on shutdown + """ + aiohttp_session = aiohttp.ClientSession() + daily_helpers["rest"] = DailyRESTHelper( + daily_api_key=os.getenv("DAILY_API_KEY", ""), + daily_api_url=os.getenv("DAILY_API_URL", "https://api.daily.co/v1"), + aiohttp_session=aiohttp_session, + ) + yield + await aiohttp_session.close() + cleanup() + + +# Initialize FastAPI app with lifespan manager +app = FastAPI(lifespan=lifespan) + +# Configure CORS to allow requests from any origin +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +async def create_room_and_token() -> tuple[str, str]: + """Helper function to create a Daily room and generate an access token. + + Returns: + tuple[str, str]: A tuple containing (room_url, token) + + Raises: + HTTPException: If room creation or token generation fails + """ + room = await daily_helpers["rest"].create_room(DailyRoomParams()) + if not room.url: + raise HTTPException(status_code=500, detail="Failed to create room") + + token = await daily_helpers["rest"].get_token(room.url) + if not token: + raise HTTPException(status_code=500, detail=f"Failed to get token for room: {room.url}") + + return room.url, token + + +@app.post("/connect") +async def bot_connect(request: Request) -> Dict[Any, Any]: + """Connect endpoint that creates a room and returns connection credentials. + + This endpoint is called by client to establish a connection. + + Returns: + Dict[Any, Any]: Authentication bundle containing room_url and token + + Raises: + HTTPException: If room creation, token generation, or bot startup fails + """ + print("Creating room for RTVI connection") + room_url, token = await create_room_and_token() + print(f"Room URL: {room_url}") + + # Start the bot process + try: + bot_file = "signalling_bot" + proc = subprocess.Popen( + [f"python3 -m {bot_file} -u {room_url} -t {token}"], + shell=True, + bufsize=1, + cwd=os.path.dirname(os.path.abspath(__file__)), + ) + bot_procs[proc.pid] = (proc, room_url) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to start subprocess: {e}") + + # Return the authentication bundle in format expected by DailyTransport + return {"room_url": room_url, "token": token} + + +if __name__ == "__main__": + import uvicorn + + # Parse command line arguments for server configuration + default_host = os.getenv("HOST", "0.0.0.0") + default_port = int(os.getenv("FAST_API_PORT", "7860")) + + parser = argparse.ArgumentParser(description="Daily Travel Companion FastAPI server") + parser.add_argument("--host", type=str, default=default_host, help="Host address") + parser.add_argument("--port", type=int, default=default_port, help="Port number") + parser.add_argument("--reload", action="store_true", help="Reload code on change") + + config = parser.parse_args() + + # Start the FastAPI server + uvicorn.run( + "server:app", + host=config.host, + port=config.port, + reload=config.reload, + ) diff --git a/examples/bot-ready-signalling/server/say-when-bot-ready.py b/examples/bot-ready-signalling/server/signalling_bot.py similarity index 100% rename from examples/bot-ready-signalling/server/say-when-bot-ready.py rename to examples/bot-ready-signalling/server/signalling_bot.py From d2efe27350e5e0a0507394ab71469d8fc4cc476d Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 10:36:45 -0300 Subject: [PATCH 3/7] Improving the logs and updating status --- .../client/javascript/src/app.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js index ea7e26164..162a3869b 100644 --- a/examples/bot-ready-signalling/client/javascript/src/app.js +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -71,7 +71,7 @@ class ChatbotClient { } handleEventToConsole (evt) { - console.log("Received event: ", evt); + this.log(`Received event: ${evt.action}`); }; /** @@ -81,18 +81,28 @@ class ChatbotClient { setupTrackListeners() { if (!this.dailyCallObject) return; - this.dailyCallObject.on("joined-meeting", this.handleEventToConsole); + this.dailyCallObject.on("joined-meeting", () => { + this.updateStatus('Connected'); + this.connectBtn.disabled = true; + this.disconnectBtn.disabled = false; + this.log('Client connected'); + }); this.dailyCallObject.on("track-started", (evt) => { if (evt.track.kind === "audio" && evt.participant.local === false) { this.setupAudioTrack(evt.track); } }); - this.dailyCallObject.on("track-stopped", this.handleEventToConsole); - this.dailyCallObject.on("participant-joined", this.handleEventToConsole); - this.dailyCallObject.on("participant-updated", this.handleEventToConsole); - this.dailyCallObject.on("participant-left", this.handleEventToConsole); - this.dailyCallObject.on("left-meeting", this.handleEventToConsole); - this.dailyCallObject.on("error", this.handleEventToConsole); + this.dailyCallObject.on("track-stopped", this.handleEventToConsole.bind(this)); + this.dailyCallObject.on("participant-joined", this.handleEventToConsole.bind(this)); + this.dailyCallObject.on("participant-updated", this.handleEventToConsole.bind(this)); + this.dailyCallObject.on("participant-left", this.handleEventToConsole.bind(this)); + this.dailyCallObject.on("left-meeting", () => { + this.updateStatus('Disconnected'); + this.connectBtn.disabled = false; + this.disconnectBtn.disabled = true; + this.log('Client disconnected'); + }); + this.dailyCallObject.on("error", this.handleEventToConsole.bind(this)); } /** From ea1323723daae298889beb4beac61b0ea94a4ddd Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 10:42:22 -0300 Subject: [PATCH 4/7] Handling the signalling to play the audio --- .../bot-ready-signalling/client/javascript/src/app.js | 9 ++++++++- examples/bot-ready-signalling/server/signalling_bot.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js index 162a3869b..afe137d77 100644 --- a/examples/bot-ready-signalling/client/javascript/src/app.js +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -89,13 +89,19 @@ class ChatbotClient { }); this.dailyCallObject.on("track-started", (evt) => { if (evt.track.kind === "audio" && evt.participant.local === false) { + this.log("Audio track started.") this.setupAudioTrack(evt.track); + this.log("Will send the audio message to play the audio") + this.dailyCallObject.sendAppMessage("playable") } }); this.dailyCallObject.on("track-stopped", this.handleEventToConsole.bind(this)); this.dailyCallObject.on("participant-joined", this.handleEventToConsole.bind(this)); this.dailyCallObject.on("participant-updated", this.handleEventToConsole.bind(this)); - this.dailyCallObject.on("participant-left", this.handleEventToConsole.bind(this)); + this.dailyCallObject.on("participant-left", () => { + // When the bot leaves, we are also disconnecting from the call + this.disconnect() + }); this.dailyCallObject.on("left-meeting", () => { this.updateStatus('Disconnected'); this.connectBtn.disabled = false; @@ -185,6 +191,7 @@ class ChatbotClient { try { // Disconnect the RTVI client await this.dailyCallObject.leave(); + await this.dailyCallObject.destroy(); this.dailyCallObject = null; // Clean up audio diff --git a/examples/bot-ready-signalling/server/signalling_bot.py b/examples/bot-ready-signalling/server/signalling_bot.py index e56113ef5..0a93d0cbc 100644 --- a/examples/bot-ready-signalling/server/signalling_bot.py +++ b/examples/bot-ready-signalling/server/signalling_bot.py @@ -47,7 +47,7 @@ async def main(): @transport.event_handler("on_app_message") async def on_app_message(transport, message, sender): logger.debug(f"Received app message: {message} - {sender}") - if "message" not in message: + if "playable" not in message: return await task.queue_frames( [TTSSpeakFrame(f"Hello there, how are you doing today ?"), EndFrame()] From 119c0da29992ecf713a4a11bd7de83be9abefc26 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 11:02:53 -0300 Subject: [PATCH 5/7] Configuring a proxy so we can test from mobile --- .../client/javascript/src/app.js | 2 +- .../client/javascript/vite.config.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 examples/bot-ready-signalling/client/javascript/vite.config.js diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js index afe137d77..135dd43e2 100644 --- a/examples/bot-ready-signalling/client/javascript/src/app.js +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -127,7 +127,7 @@ class ChatbotClient { } async fetchRoomInfo() { - let connectUrl = 'http://0.0.0.0:7860/connect' + let connectUrl = '/connect' let res = await fetch(connectUrl, { method: "POST", mode: "cors", diff --git a/examples/bot-ready-signalling/client/javascript/vite.config.js b/examples/bot-ready-signalling/client/javascript/vite.config.js new file mode 100644 index 000000000..5ccad4a6b --- /dev/null +++ b/examples/bot-ready-signalling/client/javascript/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + server: { + proxy: { + // Proxy /api requests to the backend server + '/connect': { + target: 'http://0.0.0.0:7860', // Replace with your backend URL + changeOrigin: true, + }, + }, + }, +}); From 7efd00e0f79511bbd2e0ce4c97ea60f09e79556a Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 16:00:56 -0300 Subject: [PATCH 6/7] Asking for the bot to send the audio only when the audio element is already on playing state. --- .../bot-ready-signalling/client/javascript/src/app.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/bot-ready-signalling/client/javascript/src/app.js b/examples/bot-ready-signalling/client/javascript/src/app.js index 135dd43e2..5e0232f68 100644 --- a/examples/bot-ready-signalling/client/javascript/src/app.js +++ b/examples/bot-ready-signalling/client/javascript/src/app.js @@ -91,8 +91,6 @@ class ChatbotClient { if (evt.track.kind === "audio" && evt.participant.local === false) { this.log("Audio track started.") this.setupAudioTrack(evt.track); - this.log("Will send the audio message to play the audio") - this.dailyCallObject.sendAppMessage("playable") } }); this.dailyCallObject.on("track-stopped", this.handleEventToConsole.bind(this)); @@ -116,7 +114,8 @@ class ChatbotClient { * Handles both initial setup and track updates */ setupAudioTrack(track) { - this.log('Setting up audio track'); + this.log(`Setting up audio track, track state: ${track.readyState}, muted: ${track.muted}`); + // Check if we're already playing this track if (this.botAudio.srcObject) { const oldTrack = this.botAudio.srcObject.getAudioTracks()[0]; @@ -124,6 +123,11 @@ class ChatbotClient { } // Create a new MediaStream with the track and set it as the audio source this.botAudio.srcObject = new MediaStream([track]); + this.botAudio.onplaying = async (event) => { + this.log("onplaying") + this.log("Will send the audio message to play the audio at the next tick") + this.dailyCallObject.sendAppMessage("playable") + } } async fetchRoomInfo() { From c4c15eff39d8edd64993a62c2c84472da99f3e96 Mon Sep 17 00:00:00 2001 From: Filipi Fuchter Date: Thu, 16 Jan 2025 18:30:19 -0300 Subject: [PATCH 7/7] Sending a silence frame to prevent the audio from clipping. --- .../server/signalling_bot.py | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/examples/bot-ready-signalling/server/signalling_bot.py b/examples/bot-ready-signalling/server/signalling_bot.py index 0a93d0cbc..23d407c05 100644 --- a/examples/bot-ready-signalling/server/signalling_bot.py +++ b/examples/bot-ready-signalling/server/signalling_bot.py @@ -7,13 +7,14 @@ import asyncio import os import sys +from dataclasses import dataclass import aiohttp from dotenv import load_dotenv from loguru import logger from runner import configure -from pipecat.frames.frames import EndFrame, TTSSpeakFrame +from pipecat.frames.frames import AudioRawFrame, EndFrame, OutputAudioRawFrame, TTSSpeakFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner from pipecat.pipeline.task import PipelineTask @@ -26,6 +27,34 @@ logger.add(sys.stderr, level="DEBUG") +@dataclass +class SilenceFrame(OutputAudioRawFrame): + def __init__( + self, + audio: bytes = None, + sample_rate: int = 16000, + num_channels: int = 1, + duration: float = 0.1, + ): + # Initialize the parent class with the silent frame's data + super().__init__( + audio=self.create_silent_audio_frame(sample_rate, num_channels, duration).audio, + sample_rate=sample_rate, + num_channels=num_channels, + ) + + @staticmethod + def create_silent_audio_frame( + sample_rate: int, num_channels: int, duration: float + ) -> AudioRawFrame: + """Create an AudioRawFrame containing silence.""" + frame_size = num_channels * 2 # 2 bytes per sample for 16-bit audio + total_frames = int(sample_rate * duration) + total_bytes = total_frames * frame_size + silent_audio = bytes(total_bytes) # Create a byte array filled with zeros + return AudioRawFrame(audio=silent_audio, sample_rate=sample_rate, num_channels=num_channels) + + async def main(): async with aiohttp.ClientSession() as session: (room_url, _) = await configure(session) @@ -50,7 +79,11 @@ async def on_app_message(transport, message, sender): if "playable" not in message: return await task.queue_frames( - [TTSSpeakFrame(f"Hello there, how are you doing today ?"), EndFrame()] + [ + SilenceFrame(duration=0.5), + TTSSpeakFrame(f"Hello there, how are you doing today ?"), + EndFrame(), + ] ) await runner.run(task)