Allowing settings to be changed in URL hash (#45)
Co-authored-by: mattherzog <Herzog.Matt@gmail.com> Co-authored-by: Neil Dwyer <neildwyer1991@gmail.com>
This commit is contained in:
17
.env.example
17
.env.example
@@ -10,14 +10,15 @@ NEXT_PUBLIC_APP_CONFIG="
|
||||
title: 'LiveKit Agent Playground'
|
||||
description: 'LiveKit Agent Playground allows you to test your LiveKit Agent integration by connecting to your LiveKit Cloud or self-hosted instance.'
|
||||
github_link: 'https://github.com/livekit/agents-playground'
|
||||
theme_color: 'cyan'
|
||||
video_fit: 'cover' # 'contain' or 'cover'
|
||||
outputs:
|
||||
audio: true # Enable or disable audio output
|
||||
video: true # Enable or disable video output
|
||||
settings:
|
||||
theme_color: 'cyan'
|
||||
chat: true # Enable or disable chat feature
|
||||
inputs:
|
||||
mic: true # Enable or disable microphone input
|
||||
camera: true # Enable or disable camera input
|
||||
sip: true # Enable or disable SIP input
|
||||
outputs:
|
||||
audio: true # Enable or disable audio output
|
||||
video: true # Enable or disable video output
|
||||
inputs:
|
||||
mic: true # Enable or disable microphone input
|
||||
camera: true # Enable or disable camera input
|
||||
sip: true # Enable or disable SIP input
|
||||
"
|
||||
|
||||
776
package-lock.json
generated
776
package-lock.json
generated
@@ -8,11 +8,14 @@
|
||||
"name": "agents-playground",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "^2.0.0",
|
||||
"@livekit/components-react": "^2.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"cookies-next": "^4.1.1",
|
||||
"framer-motion": "^10.16.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
"livekit-client": "^2.0.2",
|
||||
"livekit-server-sdk": "^2.0.3",
|
||||
"livekit-client": "^2.1.0",
|
||||
"livekit-server-sdk": "^2.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^14.0.4",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18",
|
||||
@@ -20,6 +23,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18",
|
||||
@@ -56,7 +60,6 @@
|
||||
"version": "7.23.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz",
|
||||
"integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@@ -141,11 +144,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.1.tgz",
|
||||
"integrity": "sha512-42UH54oPZHPdRHdw6BgoBD6cg/eVTmVrFcgeRDM3jbO7uxSoipVcmcIGFcA5jmOHO5apcyvBhkSKES3fQJnu7A==",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.1"
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
@@ -157,10 +160,22 @@
|
||||
"@floating-ui/utils": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.9.tgz",
|
||||
"integrity": "sha512-q0umO0+LQK4+p6aGyvzASqKbKOJcAHJ7ycE9CuUvfx3s9zTHWmGJTPOIlM/hmSBfUfg/XfY5YhLBLR/LHwShQQ==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
|
||||
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz",
|
||||
"integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.13",
|
||||
@@ -307,9 +322,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@livekit/components-react": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.1.3.tgz",
|
||||
"integrity": "sha512-DHn3c+buYI8O3IVQ6/9E/tWb4q9lXQomtJd7Y+9lfH4+o3ch+PAtO8aqPtpz4hdt8im0Oh1/Gim410DPgF5FSg==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.2.0.tgz",
|
||||
"integrity": "sha512-TDa2YNBphkdf2dz85pEZs1UBl8wD/LHFeYupNoTqjtlLVlTXpr09Buv3/eegQFJhXoDSK6fAYqKZ4U/oYydv/w==",
|
||||
"dependencies": {
|
||||
"@livekit/components-core": "0.10.0",
|
||||
"@react-hook/latest": "1.0.3",
|
||||
@@ -529,6 +544,535 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||
"integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
||||
"integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
|
||||
"integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-menu": "2.0.6",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz",
|
||||
"integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.4",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.3",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-roving-focus": "1.0.4",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz",
|
||||
"integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
"@radix-ui/react-arrow": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||
"@radix-ui/react-use-rect": "1.0.1",
|
||||
"@radix-ui/react-use-size": "1.0.1",
|
||||
"@radix-ui/rect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz",
|
||||
"integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-collection": "1.0.3",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-direction": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz",
|
||||
"integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/rect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz",
|
||||
"integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/rect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz",
|
||||
"integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-hook/latest": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz",
|
||||
@@ -551,6 +1095,11 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||
@@ -563,6 +1112,12 @@
|
||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||
"integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
|
||||
@@ -576,13 +1131,13 @@
|
||||
"version": "15.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
|
||||
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.55",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz",
|
||||
"integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@@ -593,7 +1148,7 @@
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
|
||||
"integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
@@ -602,7 +1157,7 @@
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "6.18.1",
|
||||
@@ -828,6 +1383,17 @@
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/aria-hidden": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
|
||||
"integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
@@ -1320,6 +1886,29 @@
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookies-next": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookies-next/-/cookies-next-4.1.1.tgz",
|
||||
"integrity": "sha512-20QaN0iQSz87Os0BhNg9M71eM++gylT3N5szTlhq2rK6QvXn1FYGPB4eAgU4qFTunbQKhD35zfQ95ZWgzUy3Cg==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^16.10.2",
|
||||
"cookie": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookies-next/node_modules/@types/node": {
|
||||
"version": "16.18.96",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz",
|
||||
"integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@@ -1350,7 +1939,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
@@ -1421,6 +2010,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@@ -2300,6 +2894,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-nonce": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||
@@ -2583,6 +3185,14 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
||||
@@ -3095,9 +3705,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/livekit-client": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.1.0.tgz",
|
||||
"integrity": "sha512-nJwfRKw1Pafd2napk66l30dlBjsv1VZ+na3mzNezcAFAYT2lQ4Gch57TdbMBDYo+QfrZ98s+kuZzsFhBwM5rqw==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.1.1.tgz",
|
||||
"integrity": "sha512-ffnXHQt210GPJ9sR846o7g0lCg/3TJqZxdu55mzQFS1YXGgn9PYKGzcAhKtuOsQ0NEkkn1zKQ0ABHBt7iADiqg==",
|
||||
"dependencies": {
|
||||
"@livekit/protocol": "1.13.0",
|
||||
"events": "^3.3.0",
|
||||
@@ -3137,6 +3747,11 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@@ -3923,6 +4538,73 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-remove-scroll": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-remove-scroll-bar": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
|
||||
"integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
|
||||
"dependencies": {
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-style-singleton": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
|
||||
"dependencies": {
|
||||
"get-nonce": "^1.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -3967,8 +4649,7 @@
|
||||
"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==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.1",
|
||||
@@ -4124,9 +4805,9 @@
|
||||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
|
||||
},
|
||||
"node_modules/sdp-transform": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz",
|
||||
"integrity": "sha512-RjZyX3nVwJyCuTo5tGPx+PZWkDMCg7oOLpSlhjDdZfwUoNqG1mM8nyj31IGHyaPWXhjbP7cdK3qZ2bmkJ1GzRw==",
|
||||
"version": "2.14.2",
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
|
||||
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
|
||||
"bin": {
|
||||
"sdp-verify": "checker.js"
|
||||
}
|
||||
@@ -4832,6 +5513,47 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-callback-ref": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
|
||||
"integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/use-sidecar": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
|
||||
"dependencies": {
|
||||
"detect-node-es": "^1.1.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/usehooks-ts": {
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.16.0.tgz",
|
||||
|
||||
10
package.json
10
package.json
@@ -9,11 +9,14 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@livekit/components-react": "^2.0.0",
|
||||
"@livekit/components-react": "^2.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"cookies-next": "^4.1.1",
|
||||
"framer-motion": "^10.16.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
"livekit-client": "^2.0.2",
|
||||
"livekit-server-sdk": "^2.0.3",
|
||||
"livekit-client": "^2.1.0",
|
||||
"livekit-server-sdk": "^2.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "^14.0.4",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18",
|
||||
@@ -21,6 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash": "^4.17.0",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18",
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { useConfig } from "@/hooks/useConfig";
|
||||
import { Button } from "./button/Button";
|
||||
import { useRef } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
type PlaygroundConnectProps = {
|
||||
accentColor: string;
|
||||
onConnectClicked: (url: string, roomToken: string) => void;
|
||||
onConnectClicked: () => void;
|
||||
};
|
||||
|
||||
export const PlaygroundConnect = ({
|
||||
accentColor,
|
||||
onConnectClicked,
|
||||
}: PlaygroundConnectProps) => {
|
||||
const urlInput = useRef<HTMLInputElement>(null);
|
||||
const tokenInput = useRef<HTMLTextAreaElement>(null);
|
||||
const { setUserSettings, config } = useConfig();
|
||||
const [url, setUrl] = useState(config.settings.ws_url)
|
||||
const [token, setToken] = useState(config.settings.token)
|
||||
|
||||
return (
|
||||
<div className="flex left-0 top-0 w-full h-full bg-black/80 items-center justify-center text-center">
|
||||
@@ -25,12 +27,14 @@ export const PlaygroundConnect = ({
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 my-4">
|
||||
<input
|
||||
ref={urlInput}
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
className="text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
|
||||
placeholder="wss://url"
|
||||
></input>
|
||||
<textarea
|
||||
ref={tokenInput}
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
className="text-white text-sm bg-transparent border border-gray-800 rounded-sm px-3 py-2"
|
||||
placeholder="room token..."
|
||||
></textarea>
|
||||
@@ -39,12 +43,11 @@ export const PlaygroundConnect = ({
|
||||
accentColor={accentColor}
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
if (urlInput.current && tokenInput.current) {
|
||||
onConnectClicked(
|
||||
urlInput.current.value,
|
||||
tokenInput.current.value
|
||||
);
|
||||
}
|
||||
const newSettings = {...config.settings};
|
||||
newSettings.ws_url = url;
|
||||
newSettings.token = token;
|
||||
setUserSettings(newSettings);
|
||||
onConnectClicked();
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
PlaygroundTile,
|
||||
} from "@/components/playground/PlaygroundTile";
|
||||
import { AgentMultibandAudioVisualizer } from "@/components/visualization/AgentMultibandAudioVisualizer";
|
||||
import { useConfig } from "@/hooks/useConfig";
|
||||
import { useMultibandTrackVolume } from "@/hooks/useTrackVolume";
|
||||
import { AgentState } from "@/lib/types";
|
||||
import {
|
||||
@@ -21,9 +22,9 @@ import {
|
||||
useConnectionState,
|
||||
useDataChannel,
|
||||
useLocalParticipant,
|
||||
useParticipantInfo,
|
||||
useRemoteParticipant,
|
||||
useRemoteParticipants,
|
||||
useRoomContext,
|
||||
useRoomInfo,
|
||||
useTracks,
|
||||
} from "@livekit/components-react";
|
||||
import {
|
||||
@@ -48,38 +49,24 @@ export interface PlaygroundMeta {
|
||||
|
||||
export interface PlaygroundProps {
|
||||
logo?: ReactNode;
|
||||
title?: string;
|
||||
githubLink?: string;
|
||||
description?: ReactNode;
|
||||
themeColors: string[];
|
||||
defaultColor: string;
|
||||
outputs?: PlaygroundOutputs[];
|
||||
showQR?: boolean;
|
||||
onConnect: (connect: boolean, opts?: { token: string; url: string }) => void;
|
||||
metadata?: PlaygroundMeta[];
|
||||
videoFit?: "contain" | "cover";
|
||||
}
|
||||
|
||||
const headerHeight = 56;
|
||||
|
||||
export default function Playground({
|
||||
logo,
|
||||
title,
|
||||
githubLink,
|
||||
description,
|
||||
outputs,
|
||||
showQR,
|
||||
themeColors,
|
||||
defaultColor,
|
||||
onConnect,
|
||||
metadata,
|
||||
videoFit,
|
||||
}: PlaygroundProps) {
|
||||
const {config, setUserSettings} = useConfig();
|
||||
const { name } = useRoomInfo();
|
||||
const [agentState, setAgentState] = useState<AgentState>("offline");
|
||||
const [themeColor, setThemeColor] = useState(defaultColor);
|
||||
const [messages, setMessages] = useState<ChatMessageType[]>([]);
|
||||
const [transcripts, setTranscripts] = useState<ChatMessageType[]>([]);
|
||||
const { localParticipant } = useLocalParticipant();
|
||||
const [outputs, setOutputs] = useState<PlaygroundOutputs[]>([]);
|
||||
|
||||
const participants = useRemoteParticipants({
|
||||
updateOnlyOn: [RoomEvent.ParticipantMetadataChanged],
|
||||
@@ -87,18 +74,16 @@ export default function Playground({
|
||||
const agentParticipant = participants.find((p) => p.isAgent);
|
||||
|
||||
const { send: sendChat, chatMessages } = useChat();
|
||||
const visualizerState = useMemo(() => {
|
||||
if (agentState === "thinking") {
|
||||
return "thinking";
|
||||
} else if (agentState === "speaking") {
|
||||
return "talking";
|
||||
}
|
||||
return "idle";
|
||||
}, [agentState]);
|
||||
|
||||
const roomState = useConnectionState();
|
||||
const tracks = useTracks();
|
||||
|
||||
useEffect(() => {
|
||||
if (roomState === ConnectionState.Connected) {
|
||||
localParticipant.setCameraEnabled(config.settings.inputs.camera);
|
||||
localParticipant.setMicrophoneEnabled(config.settings.inputs.mic);
|
||||
}
|
||||
}, [config, localParticipant, roomState]);
|
||||
|
||||
const agentAudioTrack = tracks.find(
|
||||
(trackRef) =>
|
||||
trackRef.publication.kind === Track.Kind.Audio &&
|
||||
@@ -203,7 +188,7 @@ export default function Playground({
|
||||
useDataChannel(onDataReceived);
|
||||
|
||||
const videoTileContent = useMemo(() => {
|
||||
const videoFitClassName = `object-${videoFit}`;
|
||||
const videoFitClassName = `object-${config.video_fit || "cover"}`;
|
||||
return (
|
||||
<div className="flex flex-col w-full grow text-gray-950 bg-black rounded-sm border border-gray-800 relative">
|
||||
{agentVideoTrack ? (
|
||||
@@ -219,7 +204,7 @@ export default function Playground({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [agentVideoTrack, videoFit]);
|
||||
}, [agentVideoTrack, config]);
|
||||
|
||||
const audioTileContent = useMemo(() => {
|
||||
return (
|
||||
@@ -230,7 +215,7 @@ export default function Playground({
|
||||
barWidth={30}
|
||||
minBarHeight={30}
|
||||
maxBarHeight={150}
|
||||
accentColor={themeColor}
|
||||
accentColor={config.settings.theme_color}
|
||||
accentShade={500}
|
||||
frequencies={subscribedVolumes}
|
||||
borderRadius={12}
|
||||
@@ -244,37 +229,46 @@ export default function Playground({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [agentAudioTrack, subscribedVolumes, themeColor, agentState]);
|
||||
}, [
|
||||
agentAudioTrack,
|
||||
agentState,
|
||||
config.settings.theme_color,
|
||||
subscribedVolumes,
|
||||
]);
|
||||
|
||||
const chatTileContent = useMemo(() => {
|
||||
return (
|
||||
<ChatTile
|
||||
messages={messages}
|
||||
accentColor={themeColor}
|
||||
accentColor={config.settings.theme_color}
|
||||
onSend={sendChat}
|
||||
/>
|
||||
);
|
||||
}, [messages, themeColor, sendChat]);
|
||||
}, [config.settings.theme_color, messages, sendChat]);
|
||||
|
||||
const settingsTileContent = useMemo(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 h-full w-full items-start overflow-y-auto">
|
||||
{description && (
|
||||
{config.description && (
|
||||
<ConfigurationPanelItem title="Description">
|
||||
{description}
|
||||
{config.description}
|
||||
</ConfigurationPanelItem>
|
||||
)}
|
||||
|
||||
<ConfigurationPanelItem title="Settings">
|
||||
<div className="flex flex-col gap-2">
|
||||
{metadata?.map((data, index) => (
|
||||
{localParticipant && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<NameValueRow
|
||||
key={data.name + index}
|
||||
name={data.name}
|
||||
value={data.value}
|
||||
name="Room"
|
||||
value={name}
|
||||
valueColor={`${config.settings.theme_color}-500`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<NameValueRow
|
||||
name="Participant"
|
||||
value={localParticipant.identity}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</ConfigurationPanelItem>
|
||||
<ConfigurationPanelItem title="Status">
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -289,7 +283,7 @@ export default function Playground({
|
||||
}
|
||||
valueColor={
|
||||
roomState === ConnectionState.Connected
|
||||
? `${themeColor}-500`
|
||||
? `${config.settings.theme_color}-500`
|
||||
: "gray-500"
|
||||
}
|
||||
/>
|
||||
@@ -304,7 +298,11 @@ export default function Playground({
|
||||
"false"
|
||||
)
|
||||
}
|
||||
valueColor={isAgentConnected ? `${themeColor}-500` : "gray-500"}
|
||||
valueColor={
|
||||
isAgentConnected
|
||||
? `${config.settings.theme_color}-500`
|
||||
: "gray-500"
|
||||
}
|
||||
/>
|
||||
<NameValueRow
|
||||
name="Agent status"
|
||||
@@ -319,7 +317,9 @@ export default function Playground({
|
||||
)
|
||||
}
|
||||
valueColor={
|
||||
agentState === "speaking" ? `${themeColor}-500` : "gray-500"
|
||||
agentState === "speaking"
|
||||
? `${config.settings.theme_color}-500`
|
||||
: "gray-500"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -349,14 +349,16 @@ export default function Playground({
|
||||
<ConfigurationPanelItem title="Color">
|
||||
<ColorPicker
|
||||
colors={themeColors}
|
||||
selectedColor={themeColor}
|
||||
selectedColor={config.settings.theme_color}
|
||||
onSelect={(color) => {
|
||||
setThemeColor(color);
|
||||
const userSettings = { ...config.settings };
|
||||
userSettings.theme_color = color;
|
||||
setUserSettings(userSettings);
|
||||
}}
|
||||
/>
|
||||
</ConfigurationPanelItem>
|
||||
</div>
|
||||
{showQR && (
|
||||
{config.show_qr && (
|
||||
<div className="w-full">
|
||||
<ConfigurationPanelItem title="QR Code">
|
||||
<QRCodeSVG value={window.location.href} width="128" />
|
||||
@@ -366,17 +368,18 @@ export default function Playground({
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
agentState,
|
||||
description,
|
||||
config.description,
|
||||
config.settings,
|
||||
config.show_qr,
|
||||
// metadata,
|
||||
roomState,
|
||||
isAgentConnected,
|
||||
agentState,
|
||||
localVideoTrack,
|
||||
localMicTrack,
|
||||
localMultibandVolume,
|
||||
localVideoTrack,
|
||||
metadata,
|
||||
roomState,
|
||||
themeColor,
|
||||
themeColors,
|
||||
showQR,
|
||||
setUserSettings,
|
||||
]);
|
||||
|
||||
let mobileTabs: PlaygroundTab[] = [];
|
||||
@@ -432,18 +435,18 @@ export default function Playground({
|
||||
return (
|
||||
<>
|
||||
<PlaygroundHeader
|
||||
title={title}
|
||||
title={config.title}
|
||||
logo={logo}
|
||||
githubLink={githubLink}
|
||||
githubLink={config.github_link}
|
||||
height={headerHeight}
|
||||
accentColor={themeColor}
|
||||
accentColor={config.settings.theme_color}
|
||||
connectionState={roomState}
|
||||
onConnectClicked={() =>
|
||||
onConnect(roomState === ConnectionState.Disconnected)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className={`flex gap-4 py-4 grow w-full selection:bg-${themeColor}-900`}
|
||||
className={`flex gap-4 py-4 grow w-full selection:bg-${config.settings.theme_color}-900`}
|
||||
style={{ height: `calc(100% - ${headerHeight}px)` }}
|
||||
>
|
||||
<div className="flex flex-col grow basis-1/2 gap-4 h-full lg:hidden">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Button } from "@/components/button/Button";
|
||||
import { ConnectionState } from "livekit-client";
|
||||
import { LoadingSVG } from "@/components/button/LoadingSVG";
|
||||
import { SettingsDropdown } from "@/components/playground/SettingsDropdown";
|
||||
import { ConnectionState } from "livekit-client";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type PlaygroundHeader = {
|
||||
@@ -37,7 +38,7 @@ export const PlaygroundHeader = ({
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex basis-1/3 justify-end items-center gap-4">
|
||||
<div className="flex basis-1/3 justify-end items-center gap-2">
|
||||
{githubLink && (
|
||||
<a
|
||||
href={githubLink}
|
||||
@@ -47,6 +48,7 @@ export const PlaygroundHeader = ({
|
||||
<GithubSVG />
|
||||
</a>
|
||||
)}
|
||||
<SettingsDropdown />
|
||||
<Button
|
||||
accentColor={
|
||||
connectionState === ConnectionState.Connected ? "red" : accentColor
|
||||
|
||||
128
src/components/playground/SettingsDropdown.tsx
Normal file
128
src/components/playground/SettingsDropdown.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { CheckIcon, ChevronIcon } from "./icons";
|
||||
import { useConfig } from "@/hooks/useConfig";
|
||||
|
||||
type SettingType = "inputs" | "outputs" | "chat" | "theme_color"
|
||||
|
||||
type SettingValue = {
|
||||
title: string;
|
||||
type: SettingType | "separator";
|
||||
key: string;
|
||||
};
|
||||
|
||||
const settingsDropdown: SettingValue[] = [
|
||||
{
|
||||
title: "Show chat",
|
||||
type: "chat",
|
||||
key: "N/A",
|
||||
},
|
||||
{
|
||||
title: "---",
|
||||
type: "separator",
|
||||
key: "separator_1",
|
||||
},
|
||||
{
|
||||
title: "Show video",
|
||||
type: "outputs",
|
||||
key: "video",
|
||||
},
|
||||
{
|
||||
title: "Show audio",
|
||||
type: "outputs",
|
||||
key: "audio",
|
||||
},
|
||||
|
||||
{
|
||||
title: "---",
|
||||
type: "separator",
|
||||
key: "separator_2",
|
||||
},
|
||||
{
|
||||
title: "Enable camera",
|
||||
type: "inputs",
|
||||
key: "camera",
|
||||
},
|
||||
{
|
||||
title: "Enable mic",
|
||||
type: "inputs",
|
||||
key: "mic",
|
||||
},
|
||||
];
|
||||
|
||||
export const SettingsDropdown = () => {
|
||||
const {config, setUserSettings} = useConfig();
|
||||
|
||||
const isEnabled = (setting: SettingValue) => {
|
||||
if (setting.type === "separator" || setting.type === "theme_color") return false;
|
||||
if (setting.type === "chat") {
|
||||
return config.settings[setting.type];
|
||||
}
|
||||
|
||||
if(setting.type === "inputs") {
|
||||
const key = setting.key as "camera" | "mic";
|
||||
return config.settings.inputs[key];
|
||||
} else if(setting.type === "outputs") {
|
||||
const key = setting.key as "video" | "audio";
|
||||
return config.settings.outputs[key];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const toggleSetting = (setting: SettingValue) => {
|
||||
if (setting.type === "separator" || setting.type === "theme_color") return;
|
||||
const newValue = !isEnabled(setting);
|
||||
const newSettings = {...config.settings}
|
||||
|
||||
if(setting.type === "chat") {
|
||||
newSettings.chat = newValue;
|
||||
} else if(setting.type === "inputs") {
|
||||
newSettings.inputs[setting.key as "camera" | "mic"] = newValue;
|
||||
} else if(setting.type === "outputs") {
|
||||
newSettings.outputs[setting.key as "video" | "audio"] = newValue;
|
||||
}
|
||||
setUserSettings(newSettings);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root modal={false}>
|
||||
<DropdownMenu.Trigger className="group inline-flex max-h-12 items-center gap-1 rounded-md hover:bg-gray-800 bg-gray-900 border-gray-800 p-1 pr-2 text-gray-100">
|
||||
<button className="my-auto text-sm flex gap-1 pl-2 py-1 h-full items-center">
|
||||
Settings
|
||||
<ChevronIcon />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="z-50 flex w-60 flex-col gap-0 overflow-hidden rounded text-gray-100 border border-gray-800 bg-gray-900 py-2 text-sm"
|
||||
sideOffset={5}
|
||||
collisionPadding={16}
|
||||
>
|
||||
{settingsDropdown.map((setting) => {
|
||||
if (setting.type === "separator") {
|
||||
return (
|
||||
<div
|
||||
key={setting.key}
|
||||
className="border-t border-gray-800 my-2"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu.Label
|
||||
key={setting.key}
|
||||
onClick={() => toggleSetting(setting)}
|
||||
className="flex max-w-full flex-row items-end gap-2 px-3 py-2 text-xs hover:bg-gray-800 cursor-pointer"
|
||||
>
|
||||
<div className="w-4 h-4 flex items-center">
|
||||
{isEnabled(setting) && <CheckIcon />}
|
||||
</div>
|
||||
<span>{setting.title}</span>
|
||||
</DropdownMenu.Label>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Root>
|
||||
);
|
||||
};
|
||||
39
src/components/playground/icons.tsx
Normal file
39
src/components/playground/icons.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
export const CheckIcon = () => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0_718_9977)">
|
||||
<path
|
||||
d="M1.5 7.5L4.64706 10L10.5 2"
|
||||
stroke="white"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_718_9977">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ChevronIcon = () => (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="fill-gray-200 transition-all group-hover:fill-white group-data-[state=open]:rotate-180"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="m8 10.7.4-.3 4-4 .3-.4-.7-.7-.4.3L8 9.3 4.4 5.6 4 5.3l-.7.7.3.4 4 4 .4.3Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,54 +0,0 @@
|
||||
import jsYaml from "js-yaml";
|
||||
|
||||
const APP_CONFIG = process.env.NEXT_PUBLIC_APP_CONFIG;
|
||||
|
||||
export type AppConfig = {
|
||||
title: string;
|
||||
description: string;
|
||||
github_link?: string;
|
||||
theme_color?: string;
|
||||
video_fit?: "cover" | "contain";
|
||||
outputs: {
|
||||
audio: boolean;
|
||||
video: boolean;
|
||||
chat: boolean;
|
||||
};
|
||||
inputs: {
|
||||
mic: boolean;
|
||||
camera: boolean;
|
||||
};
|
||||
show_qr?: boolean;
|
||||
};
|
||||
|
||||
// Fallback if NEXT_PUBLIC_APP_CONFIG is not set
|
||||
const defaultConfig: AppConfig = {
|
||||
title: "Agents Playground",
|
||||
description: "A playground for testing LiveKit Agents",
|
||||
theme_color: "cyan",
|
||||
video_fit: "cover",
|
||||
outputs: {
|
||||
audio: true,
|
||||
video: true,
|
||||
chat: true,
|
||||
},
|
||||
inputs: {
|
||||
mic: true,
|
||||
camera: true,
|
||||
},
|
||||
show_qr: false,
|
||||
};
|
||||
|
||||
export const useAppConfig = (): AppConfig => {
|
||||
if (APP_CONFIG) {
|
||||
try {
|
||||
const parsedConfig = jsYaml.load(APP_CONFIG);
|
||||
console.log("parsedConfig:", parsedConfig);
|
||||
return parsedConfig as AppConfig;
|
||||
} catch (e) {
|
||||
console.error("Error parsing app config:", e);
|
||||
return defaultConfig;
|
||||
}
|
||||
} else {
|
||||
return defaultConfig;
|
||||
}
|
||||
};
|
||||
198
src/hooks/useConfig.tsx
Normal file
198
src/hooks/useConfig.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
"use client"
|
||||
|
||||
import { getCookie, setCookie } from "cookies-next";
|
||||
import jsYaml from "js-yaml";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React, { createContext, useCallback, useMemo, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export type AppConfig = {
|
||||
title: string;
|
||||
description: string;
|
||||
github_link?: string;
|
||||
video_fit?: "cover" | "contain";
|
||||
settings: UserSettings;
|
||||
show_qr?: boolean;
|
||||
};
|
||||
|
||||
export type UserSettings = {
|
||||
theme_color: string;
|
||||
chat: boolean;
|
||||
inputs: {
|
||||
camera: boolean;
|
||||
mic: boolean;
|
||||
};
|
||||
outputs: {
|
||||
audio: boolean;
|
||||
video: boolean;
|
||||
};
|
||||
ws_url: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
// Fallback if NEXT_PUBLIC_APP_CONFIG is not set
|
||||
const defaultConfig: AppConfig = {
|
||||
title: "LiveKit Agents Playground",
|
||||
description: "A playground for testing LiveKit Agents",
|
||||
video_fit: "cover",
|
||||
settings: {
|
||||
theme_color: "cyan",
|
||||
chat: true,
|
||||
inputs: {
|
||||
camera: true,
|
||||
mic: true,
|
||||
},
|
||||
outputs: {
|
||||
audio: true,
|
||||
video: true,
|
||||
},
|
||||
ws_url: "",
|
||||
token: ""
|
||||
},
|
||||
show_qr: false,
|
||||
};
|
||||
|
||||
const useAppConfig = (): AppConfig => {
|
||||
return useMemo(() => {
|
||||
if (process.env.NEXT_PUBLIC_APP_CONFIG) {
|
||||
try {
|
||||
const parsedConfig = jsYaml.load(
|
||||
process.env.NEXT_PUBLIC_APP_CONFIG
|
||||
) as AppConfig;
|
||||
return parsedConfig;
|
||||
} catch (e) {
|
||||
console.error("Error parsing app config:", e);
|
||||
}
|
||||
}
|
||||
return defaultConfig;
|
||||
}, []);
|
||||
};
|
||||
|
||||
type ConfigData = {
|
||||
config: AppConfig;
|
||||
setUserSettings: (settings: UserSettings) => void;
|
||||
};
|
||||
|
||||
const ConfigContext = createContext<ConfigData | undefined>(undefined);
|
||||
|
||||
export const ConfigProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const appConfig = useAppConfig();
|
||||
const router = useRouter();
|
||||
|
||||
const getSettingsFromUrl = useCallback(() => {
|
||||
if(typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
if (!window.location.hash) {
|
||||
return null;
|
||||
}
|
||||
const params = new URLSearchParams(window.location.hash.replace("#", ""));
|
||||
return {
|
||||
chat: params.get("chat") === "1",
|
||||
theme_color: params.get("theme_color"),
|
||||
inputs: {
|
||||
camera: params.get("cam") === "1",
|
||||
mic: params.get("mic") === "1",
|
||||
},
|
||||
outputs: {
|
||||
audio: params.get("audio") === "1",
|
||||
video: params.get("video") === "1",
|
||||
chat: params.get("chat") === "1",
|
||||
},
|
||||
ws_url: "",
|
||||
token: ""
|
||||
} as UserSettings;
|
||||
}, [])
|
||||
|
||||
const getSettingsFromCookies = useCallback(() => {
|
||||
const jsonSettings = getCookie("lk_settings");
|
||||
if (!jsonSettings) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(jsonSettings) as UserSettings;
|
||||
}, [])
|
||||
|
||||
const setUrlSettings = useCallback((us: UserSettings) => {
|
||||
const obj = new URLSearchParams({
|
||||
cam: boolToString(us.inputs.camera),
|
||||
mic: boolToString(us.inputs.mic),
|
||||
video: boolToString(us.outputs.video),
|
||||
audio: boolToString(us.outputs.audio),
|
||||
chat: boolToString(us.chat),
|
||||
theme_color: us.theme_color || "cyan",
|
||||
});
|
||||
// Note: We don't set ws_url and token to the URL on purpose
|
||||
router.replace("/#" + obj.toString());
|
||||
}, [router])
|
||||
|
||||
const setCookieSettings = useCallback((us: UserSettings) => {
|
||||
const json = JSON.stringify(us);
|
||||
setCookie("lk_settings", json);
|
||||
}, [])
|
||||
|
||||
const getConfig = useCallback(() => {
|
||||
const appConfigFromSettings = appConfig;
|
||||
const cookieSettigs = getSettingsFromCookies();
|
||||
const urlSettings = getSettingsFromUrl();
|
||||
if(!cookieSettigs) {
|
||||
if(urlSettings) {
|
||||
setCookieSettings(urlSettings);
|
||||
}
|
||||
}
|
||||
if(!urlSettings) {
|
||||
if(cookieSettigs) {
|
||||
setUrlSettings(cookieSettigs);
|
||||
}
|
||||
}
|
||||
const newCookieSettings = getSettingsFromCookies();
|
||||
if(!newCookieSettings) {
|
||||
return appConfigFromSettings;
|
||||
}
|
||||
appConfigFromSettings.settings = newCookieSettings;
|
||||
return {...appConfigFromSettings};
|
||||
}, [
|
||||
appConfig,
|
||||
getSettingsFromCookies,
|
||||
getSettingsFromUrl,
|
||||
setCookieSettings,
|
||||
setUrlSettings,
|
||||
]);
|
||||
|
||||
const setUserSettings = useCallback((settings: UserSettings) => {
|
||||
setUrlSettings(settings);
|
||||
setCookieSettings(settings);
|
||||
_setConfig((prev) => {
|
||||
return {
|
||||
...prev,
|
||||
settings: settings,
|
||||
};
|
||||
})
|
||||
}, [setCookieSettings, setUrlSettings]);
|
||||
|
||||
const [config, _setConfig] = useState<AppConfig>(getConfig());
|
||||
|
||||
// Run things client side because we use cookies
|
||||
useEffect(() => {
|
||||
_setConfig(getConfig());
|
||||
}, [getConfig]);
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{ config, setUserSettings }}>
|
||||
{children}
|
||||
</ConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useConfig = () => {
|
||||
const context = React.useContext(ConfigContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useConfig must be used within a ConfigProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
const boolToString = (b: boolean) => (b ? "1" : "0");
|
||||
89
src/hooks/useTokenGenerator.tsx
Normal file
89
src/hooks/useTokenGenerator.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useState } from "react";
|
||||
import { useConfig } from "./useConfig";
|
||||
import { useCallback } from "react";
|
||||
|
||||
// Note: cloud mode is only used in our private, hosted version
|
||||
export type Mode = "cloud" | "env" | "manual";
|
||||
|
||||
type TokenGeneratorData = {
|
||||
shouldConnect: boolean;
|
||||
wsUrl: string;
|
||||
token: string;
|
||||
disconnect: () => Promise<void>;
|
||||
connect: (mode: Mode) => Promise<void>;
|
||||
};
|
||||
|
||||
const TokenGeneratorContext = createContext<TokenGeneratorData | undefined>(undefined);
|
||||
|
||||
export const TokenGeneratorProvider = ({
|
||||
children,
|
||||
generateConnectionDetails,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
// generateConnectionDetails is only required in cloud mode
|
||||
generateConnectionDetails?: () => Promise<{ wsUrl: string; token: string }>;
|
||||
}) => {
|
||||
const { config } = useConfig();
|
||||
const [token, setToken] = useState("");
|
||||
const [wsUrl, setWsUrl] = useState("");
|
||||
const [shouldConnect, setShouldConnect] = useState(false);
|
||||
const connect = useCallback(
|
||||
async (mode: Mode) => {
|
||||
if (mode === "cloud") {
|
||||
if (!generateConnectionDetails) {
|
||||
throw new Error(
|
||||
"generateConnectionDetails must be provided in cloud mode"
|
||||
);
|
||||
}
|
||||
const { wsUrl, token } = await generateConnectionDetails();
|
||||
setWsUrl(wsUrl);
|
||||
setToken(token);
|
||||
} else if (mode === "env") {
|
||||
const url = process.env.NEXT_PUBLIC_LIVEKIT_URL;
|
||||
if (!url) {
|
||||
throw new Error("NEXT_PUBLIC_LIVEKIT_URL must be set in env mode");
|
||||
}
|
||||
const res = await fetch("/api/token");
|
||||
const { accessToken } = await res.json();
|
||||
setWsUrl(url);
|
||||
setToken(accessToken);
|
||||
} else if (mode === "manual") {
|
||||
setWsUrl(config.settings.ws_url);
|
||||
setToken(config.settings.token);
|
||||
}
|
||||
setShouldConnect(true);
|
||||
},
|
||||
[
|
||||
config.settings.token,
|
||||
config.settings.ws_url,
|
||||
generateConnectionDetails,
|
||||
]
|
||||
);
|
||||
const disconnect = useCallback(async () => {
|
||||
setShouldConnect(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TokenGeneratorContext.Provider
|
||||
value={{
|
||||
wsUrl,
|
||||
token,
|
||||
shouldConnect,
|
||||
connect,
|
||||
disconnect,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TokenGeneratorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTokenGenerator = () => {
|
||||
const context = React.useContext(TokenGeneratorContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useTokenGenerator must be used within a TokenGeneratorProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { generateRandomAlphanumeric } from "@/lib/util";
|
||||
|
||||
import { AccessToken } from "livekit-server-sdk";
|
||||
import type { AccessTokenOptions, VideoGrant } from "livekit-server-sdk";
|
||||
@@ -13,48 +14,19 @@ const createToken = (userInfo: AccessTokenOptions, grant: VideoGrant) => {
|
||||
return at.toJwt();
|
||||
};
|
||||
|
||||
const roomPattern = /\w{4}\-\w{4}/;
|
||||
|
||||
export default async function handleToken(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
const { roomName, identity, name, metadata } = req.query;
|
||||
|
||||
if (typeof identity !== "string" || typeof roomName !== "string") {
|
||||
res.statusMessage =
|
||||
"identity and roomName have to be specified in the request";
|
||||
res.status(403).end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiKey || !apiSecret) {
|
||||
res.statusMessage = "Environment variables aren't set up correctly";
|
||||
res.status(500).end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(name)) {
|
||||
throw Error("provide max one name");
|
||||
}
|
||||
if (Array.isArray(metadata)) {
|
||||
throw Error("provide max one metadata string");
|
||||
}
|
||||
|
||||
// enforce room name to be xxxx-xxxx
|
||||
// this is simple & naive way to prevent user from guessing room names
|
||||
// please use your own authentication mechanisms in your own app
|
||||
if (!roomName.match(roomPattern)) {
|
||||
res.statusMessage = "Invalid roomName";
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!userSession.isAuthenticated) {
|
||||
// res.status(403).end();
|
||||
// return;
|
||||
// }
|
||||
const roomName = `room-${generateRandomAlphanumeric(4)}-${generateRandomAlphanumeric(4)}`;
|
||||
const identity = `identity-${generateRandomAlphanumeric(4)}`
|
||||
|
||||
const grant: VideoGrant = {
|
||||
room: roomName,
|
||||
@@ -64,7 +36,7 @@ export default async function handleToken(
|
||||
canSubscribe: true,
|
||||
};
|
||||
|
||||
const token = await createToken({ identity, name, metadata }, grant);
|
||||
const token = await createToken({ identity }, grant);
|
||||
const result: TokenResult = {
|
||||
identity,
|
||||
accessToken: token,
|
||||
@@ -75,4 +47,4 @@ export default async function handleToken(
|
||||
res.statusMessage = (e as Error).message;
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
import { generateRandomAlphanumeric } from "@/lib/util";
|
||||
import {
|
||||
LiveKitRoom,
|
||||
RoomAudioRenderer,
|
||||
StartAudio,
|
||||
useToken,
|
||||
} from "@livekit/components-react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Inter } from "next/font/google";
|
||||
import Head from "next/head";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { PlaygroundConnect } from "@/components/PlaygroundConnect";
|
||||
import Playground, {
|
||||
PlaygroundMeta,
|
||||
PlaygroundOutputs,
|
||||
} from "@/components/playground/Playground";
|
||||
import Playground, { PlaygroundMeta } from "@/components/playground/Playground";
|
||||
import { PlaygroundToast, ToastType } from "@/components/toast/PlaygroundToast";
|
||||
import { useAppConfig } from "@/hooks/useAppConfig";
|
||||
import { ConfigProvider, useConfig } from "@/hooks/useConfig";
|
||||
import { Mode, TokenGeneratorProvider, useTokenGenerator } from "@/hooks/useTokenGenerator";
|
||||
import { useRef } from "react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const themeColors = [
|
||||
"cyan",
|
||||
@@ -32,77 +30,47 @@ const themeColors = [
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<TokenGeneratorProvider>
|
||||
<HomeInner />
|
||||
</TokenGeneratorProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function HomeInner() {
|
||||
const [toastMessage, setToastMessage] = useState<{
|
||||
message: string;
|
||||
type: ToastType;
|
||||
} | null>(null);
|
||||
const [shouldConnect, setShouldConnect] = useState(false);
|
||||
const [liveKitUrl, setLiveKitUrl] = useState(
|
||||
process.env.NEXT_PUBLIC_LIVEKIT_URL
|
||||
);
|
||||
const [customToken, setCustomToken] = useState<string>();
|
||||
const [metadata, setMetadata] = useState<PlaygroundMeta[]>([]);
|
||||
const { shouldConnect, wsUrl, token, connect, disconnect } =
|
||||
useTokenGenerator();
|
||||
|
||||
const [roomName, setRoomName] = useState(createRoomName());
|
||||
|
||||
const tokenOptions = useMemo(() => {
|
||||
return {
|
||||
userInfo: { identity: generateRandomAlphanumeric(16) },
|
||||
};
|
||||
}, []);
|
||||
|
||||
// set a new room name each time the user disconnects so that a new token gets fetched behind the scenes for a different room
|
||||
useEffect(() => {
|
||||
if (shouldConnect === false) {
|
||||
setRoomName(createRoomName());
|
||||
}
|
||||
}, [shouldConnect]);
|
||||
|
||||
useEffect(() => {
|
||||
const md: PlaygroundMeta[] = [];
|
||||
if (liveKitUrl && liveKitUrl !== process.env.NEXT_PUBLIC_LIVEKIT_URL) {
|
||||
md.push({ name: "LiveKit URL", value: liveKitUrl });
|
||||
}
|
||||
if (!customToken && tokenOptions.userInfo?.identity) {
|
||||
md.push({ name: "Room Name", value: roomName });
|
||||
md.push({
|
||||
name: "Participant Identity",
|
||||
value: tokenOptions.userInfo.identity,
|
||||
});
|
||||
}
|
||||
setMetadata(md);
|
||||
}, [liveKitUrl, roomName, tokenOptions, customToken]);
|
||||
|
||||
const token = useToken("/api/token", roomName, tokenOptions);
|
||||
const appConfig = useAppConfig();
|
||||
const outputs = [
|
||||
appConfig?.outputs.audio && PlaygroundOutputs.Audio,
|
||||
appConfig?.outputs.video && PlaygroundOutputs.Video,
|
||||
appConfig?.outputs.chat && PlaygroundOutputs.Chat,
|
||||
].filter((item) => typeof item !== "boolean") as PlaygroundOutputs[];
|
||||
const {config} = useConfig();
|
||||
|
||||
const handleConnect = useCallback(
|
||||
(connect: boolean, opts?: { url: string; token: string }) => {
|
||||
if (connect && opts) {
|
||||
setLiveKitUrl(opts.url);
|
||||
setCustomToken(opts.token);
|
||||
}
|
||||
setShouldConnect(connect);
|
||||
(c: boolean, mode: Mode) => {
|
||||
c ? connect(mode) : disconnect();
|
||||
},
|
||||
[]
|
||||
[connect, disconnect]
|
||||
);
|
||||
|
||||
const showPG = useMemo(() => {
|
||||
if (process.env.NEXT_PUBLIC_LIVEKIT_URL) {
|
||||
return true;
|
||||
}
|
||||
if(wsUrl) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [wsUrl])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{appConfig?.title ?? "LiveKit Agents Playground"}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
appConfig?.description ??
|
||||
"Quickly prototype and test your multimodal agents"
|
||||
}
|
||||
/>
|
||||
<title>{config.title}</title>
|
||||
<meta name="description" content={config.description} />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
@@ -136,13 +104,11 @@ export default function Home() {
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{liveKitUrl ? (
|
||||
{showPG ? (
|
||||
<LiveKitRoom
|
||||
className="flex flex-col h-full w-full"
|
||||
serverUrl={liveKitUrl}
|
||||
token={customToken ?? token}
|
||||
audio={appConfig?.inputs.mic}
|
||||
video={appConfig?.inputs.camera}
|
||||
serverUrl={wsUrl}
|
||||
token={token}
|
||||
connect={shouldConnect}
|
||||
onError={(e) => {
|
||||
setToastMessage({ message: e.message, type: "error" });
|
||||
@@ -150,16 +116,13 @@ export default function Home() {
|
||||
}}
|
||||
>
|
||||
<Playground
|
||||
title={appConfig?.title}
|
||||
githubLink={appConfig?.github_link}
|
||||
outputs={outputs}
|
||||
showQR={appConfig?.show_qr}
|
||||
description={appConfig?.description}
|
||||
themeColors={themeColors}
|
||||
defaultColor={appConfig?.theme_color ?? "cyan"}
|
||||
onConnect={handleConnect}
|
||||
metadata={metadata}
|
||||
videoFit={appConfig?.video_fit ?? "cover"}
|
||||
onConnect={(c) => {
|
||||
const mode = process.env.NEXT_PUBLIC_LIVEKIT_URL
|
||||
? "env"
|
||||
: "manual";
|
||||
handleConnect(c, mode);
|
||||
}}
|
||||
/>
|
||||
<RoomAudioRenderer />
|
||||
<StartAudio label="Click to enable audio playback" />
|
||||
@@ -167,18 +130,13 @@ export default function Home() {
|
||||
) : (
|
||||
<PlaygroundConnect
|
||||
accentColor={themeColors[0]}
|
||||
onConnectClicked={(url, token) => {
|
||||
handleConnect(true, { url, token });
|
||||
onConnectClicked={() => {
|
||||
const mode = process.env.NEXT_PUBLIC_LIVEKIT_URL ? "env" : "manual";
|
||||
handleConnect(true, mode);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function createRoomName() {
|
||||
return [generateRandomAlphanumeric(4), generateRandomAlphanumeric(4)].join(
|
||||
"-"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,6 @@ for (const [name, color] of Object.entries(customColors)) {
|
||||
shadowNames.push(`hover:shadow-${name}`);
|
||||
}
|
||||
|
||||
console.log(customShadows, textShadows);
|
||||
|
||||
const safelist = [
|
||||
'bg-black',
|
||||
'bg-white',
|
||||
@@ -50,8 +48,6 @@ const safelist = [
|
||||
]),
|
||||
];
|
||||
|
||||
console.log("Safe list", safelist);
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
|
||||
Reference in New Issue
Block a user