rapida-react
Version:
An easy to use react client for building generative ai application using Rapida platform.
1 lines • 81.5 kB
Source Map (JSON)
{"version":3,"sources":["../src/clients/protos/talk-api_pb_service.js","../src/hooks/use-message-feedback.ts","../src/hooks/use-conversation-feedback.ts","../src/hooks/use-connect-agent.ts","../src/hooks/use-input-mode-toggle-agent.ts","../src/hooks/use-speaker-output-toggle-agent.ts","../src/devices/device-failure.ts","../src/agents/agent-config.ts","../src/utils/rapida_value.ts","../src/hooks/use-multiband-track-volume.ts","../src/connections/connection-config.ts","../src/configs/index.ts"],"sourcesContent":["// package: talk_api\n// file: talk-api.proto\n\nvar talk_api_pb = require(\"./talk-api_pb\");\nvar common_pb = require(\"./common_pb\");\nvar grpc = require(\"@improbable-eng/grpc-web\").grpc;\n\nvar TalkService = (function () {\n function TalkService() {}\n TalkService.serviceName = \"talk_api.TalkService\";\n return TalkService;\n}());\n\nTalkService.AssistantMessaging = {\n methodName: \"AssistantMessaging\",\n service: TalkService,\n requestStream: false,\n responseStream: true,\n requestType: talk_api_pb.AssistantMessagingRequest,\n responseType: talk_api_pb.AssistantMessagingResponse\n};\n\nTalkService.AssistantTalk = {\n methodName: \"AssistantTalk\",\n service: TalkService,\n requestStream: true,\n responseStream: true,\n requestType: talk_api_pb.AssistantMessagingRequest,\n responseType: talk_api_pb.AssistantMessagingResponse\n};\n\nTalkService.GetAllAssistantConversation = {\n methodName: \"GetAllAssistantConversation\",\n service: TalkService,\n requestStream: false,\n responseStream: false,\n requestType: common_pb.GetAllAssistantConversationRequest,\n responseType: common_pb.GetAllAssistantConversationResponse\n};\n\nTalkService.GetAllConversationMessage = {\n methodName: \"GetAllConversationMessage\",\n service: TalkService,\n requestStream: false,\n responseStream: false,\n requestType: common_pb.GetAllConversationMessageRequest,\n responseType: common_pb.GetAllConversationMessageResponse\n};\n\nTalkService.CreateMessageMetric = {\n methodName: \"CreateMessageMetric\",\n service: TalkService,\n requestStream: false,\n responseStream: false,\n requestType: talk_api_pb.CreateMessageMetricRequest,\n responseType: talk_api_pb.CreateMessageMetricResponse\n};\n\nTalkService.CreateConversationMetric = {\n methodName: \"CreateConversationMetric\",\n service: TalkService,\n requestStream: false,\n responseStream: false,\n requestType: talk_api_pb.CreateConversationMetricRequest,\n responseType: talk_api_pb.CreateConversationMetricResponse\n};\n\nexports.TalkService = TalkService;\n\nfunction TalkServiceClient(serviceHost, options) {\n this.serviceHost = serviceHost;\n this.options = options || {};\n}\n\nTalkServiceClient.prototype.assistantMessaging = function assistantMessaging(requestMessage, metadata) {\n var listeners = {\n data: [],\n end: [],\n status: []\n };\n var client = grpc.invoke(TalkService.AssistantMessaging, {\n request: requestMessage,\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport,\n debug: this.options.debug,\n onMessage: function (responseMessage) {\n listeners.data.forEach(function (handler) {\n handler(responseMessage);\n });\n },\n onEnd: function (status, statusMessage, trailers) {\n listeners.status.forEach(function (handler) {\n handler({ code: status, details: statusMessage, metadata: trailers });\n });\n listeners.end.forEach(function (handler) {\n handler({ code: status, details: statusMessage, metadata: trailers });\n });\n listeners = null;\n }\n });\n return {\n on: function (type, handler) {\n listeners[type].push(handler);\n return this;\n },\n cancel: function () {\n listeners = null;\n client.close();\n }\n };\n};\n\nTalkServiceClient.prototype.assistantTalk = function assistantTalk(metadata) {\n var listeners = {\n data: [],\n end: [],\n status: []\n };\n var client = grpc.client(TalkService.AssistantTalk, {\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport\n });\n client.onEnd(function (status, statusMessage, trailers) {\n listeners.status.forEach(function (handler) {\n handler({ code: status, details: statusMessage, metadata: trailers });\n });\n listeners.end.forEach(function (handler) {\n handler({ code: status, details: statusMessage, metadata: trailers });\n });\n listeners = null;\n });\n client.onMessage(function (message) {\n listeners.data.forEach(function (handler) {\n handler(message);\n })\n });\n client.start(metadata);\n return {\n on: function (type, handler) {\n listeners[type].push(handler);\n return this;\n },\n write: function (requestMessage) {\n client.send(requestMessage);\n return this;\n },\n end: function () {\n client.finishSend();\n },\n cancel: function () {\n listeners = null;\n client.close();\n }\n };\n};\n\nTalkServiceClient.prototype.getAllAssistantConversation = function getAllAssistantConversation(requestMessage, metadata, callback) {\n if (arguments.length === 2) {\n callback = arguments[1];\n }\n var client = grpc.unary(TalkService.GetAllAssistantConversation, {\n request: requestMessage,\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport,\n debug: this.options.debug,\n onEnd: function (response) {\n if (callback) {\n if (response.status !== grpc.Code.OK) {\n var err = new Error(response.statusMessage);\n err.code = response.status;\n err.metadata = response.trailers;\n callback(err, null);\n } else {\n callback(null, response.message);\n }\n }\n }\n });\n return {\n cancel: function () {\n callback = null;\n client.close();\n }\n };\n};\n\nTalkServiceClient.prototype.getAllConversationMessage = function getAllConversationMessage(requestMessage, metadata, callback) {\n if (arguments.length === 2) {\n callback = arguments[1];\n }\n var client = grpc.unary(TalkService.GetAllConversationMessage, {\n request: requestMessage,\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport,\n debug: this.options.debug,\n onEnd: function (response) {\n if (callback) {\n if (response.status !== grpc.Code.OK) {\n var err = new Error(response.statusMessage);\n err.code = response.status;\n err.metadata = response.trailers;\n callback(err, null);\n } else {\n callback(null, response.message);\n }\n }\n }\n });\n return {\n cancel: function () {\n callback = null;\n client.close();\n }\n };\n};\n\nTalkServiceClient.prototype.createMessageMetric = function createMessageMetric(requestMessage, metadata, callback) {\n if (arguments.length === 2) {\n callback = arguments[1];\n }\n var client = grpc.unary(TalkService.CreateMessageMetric, {\n request: requestMessage,\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport,\n debug: this.options.debug,\n onEnd: function (response) {\n if (callback) {\n if (response.status !== grpc.Code.OK) {\n var err = new Error(response.statusMessage);\n err.code = response.status;\n err.metadata = response.trailers;\n callback(err, null);\n } else {\n callback(null, response.message);\n }\n }\n }\n });\n return {\n cancel: function () {\n callback = null;\n client.close();\n }\n };\n};\n\nTalkServiceClient.prototype.createConversationMetric = function createConversationMetric(requestMessage, metadata, callback) {\n if (arguments.length === 2) {\n callback = arguments[1];\n }\n var client = grpc.unary(TalkService.CreateConversationMetric, {\n request: requestMessage,\n host: this.serviceHost,\n metadata: metadata,\n transport: this.options.transport,\n debug: this.options.debug,\n onEnd: function (response) {\n if (callback) {\n if (response.status !== grpc.Code.OK) {\n var err = new Error(response.statusMessage);\n err.code = response.status;\n err.metadata = response.trailers;\n callback(err, null);\n } else {\n callback(null, response.message);\n }\n }\n }\n });\n return {\n cancel: function () {\n callback = null;\n client.close();\n }\n };\n};\n\nexports.TalkServiceClient = TalkServiceClient;\n\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport * as React from \"react\";\nimport { VoiceAgent } from \"@/rapida/agents/voice-agent\";\nimport { useEnsureVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\nimport { Feedback } from \"@/rapida/agents/feedback\";\n\n/**\n * Custom hook for managing agent connection in a voice system.\n * @returns An object containing the connection handler and connection status.\n */\nexport function useMessageFeedback() {\n // Get the voice agent instance\n const agent = useEnsureVoiceAgent();\n\n // Set up the connect agent and memoize the result\n const { handleMessageFeedback, handleHelpfulnessFeedback } = React.useMemo(\n () => setupMessageFeedback(agent),\n []\n );\n\n // Return the connection handler and the current connection status\n return { handleMessageFeedback, handleHelpfulnessFeedback };\n}\n\n/**\n * Sets up the connection for a voice agent.\n *\n * @returns An object containing the agent connection state observable and a function to handle agent connection.\n */\nfunction setupMessageFeedback(agent: VoiceAgent) {\n /**\n * Handles the connection of a voice agent.\n *\n * @param agent - The VoiceAgent to be connected.\n * @returns A promise that resolves when the agent is connected.\n */\n const handleMessageFeedback = async (\n messageId: string,\n name: string,\n description: string,\n value: string\n ) => {\n await agent.createMessageMetric(messageId, [\n {\n name: name,\n description: description,\n value: value,\n },\n ]);\n };\n\n const handleHelpfulnessFeedback = async (\n messageId: string,\n value: Feedback\n ) => {\n await agent.createMessageMetric(messageId, [\n {\n name: \"feedback\",\n description: \"feedback given by end-user\",\n value: value,\n },\n ]);\n };\n\n return {\n handleHelpfulnessFeedback,\n handleMessageFeedback,\n };\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport * as React from \"react\";\nimport { VoiceAgent } from \"@/rapida/agents/voice-agent\";\nimport { useEnsureVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\nimport { Feedback } from \"@/rapida/agents/feedback\";\n\n/**\n * Custom hook for managing agent connection in a voice system.\n * @returns An object containing the connection handler and connection status.\n */\nexport function useConversationFeedback() {\n // Get the voice agent instance\n const agent = useEnsureVoiceAgent();\n\n // Set up the connect agent and memoize the result\n const { handleHelpfulnessFeedback, handleConversationFeedback } =\n React.useMemo(() => setupConversationFeedback(agent), []);\n\n // Return the connection handler and the current connection status\n return { handleHelpfulnessFeedback, handleConversationFeedback };\n}\n\n/**\n * Sets up the connection for a voice agent.\n *\n * @returns An object containing the agent connection state observable and a function to handle agent connection.\n */\nfunction setupConversationFeedback(agent: VoiceAgent) {\n /**\n * Handles the connection of a voice agent.\n *\n * @param agent - The VoiceAgent to be connected.\n * @returns A promise that resolves when the agent is connected.\n */\n const handleConversationFeedback = async (\n name: string,\n description: string,\n value: string\n ) => {\n await agent.createConversationMetric([\n {\n name: name,\n description: description,\n value: value,\n },\n ]);\n };\n\n const handleHelpfulnessFeedback = async (value: Feedback) => {\n await agent.createConversationMetric([\n {\n name: \"feedback\",\n description: \"feedback given by end-user\",\n value: value,\n },\n ]);\n };\n\n return {\n handleHelpfulnessFeedback,\n handleConversationFeedback,\n };\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport * as React from \"react\";\nimport { VoiceAgent } from \"@/rapida/agents/voice-agent\";\nimport { useEnsureVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\nimport { useObservableState } from \"@/rapida/hooks/use-observable-state\";\nimport { agentConnectionStateObservable } from \"@/rapida/hooks/observables/voice-agent\";\n\n/**\n * Custom hook for managing agent connection in a voice system.\n * @returns An object containing the connection handler and connection status.\n */\nexport function useConnectAgent() {\n // Get the voice agent instance\n const agent = useEnsureVoiceAgent();\n\n // Set up the connect agent and memoize the result\n const {\n _agentConnectionStateObservable,\n handleConnectAgent,\n handleDisconnectAgent,\n } = React.useMemo(() => setupConnectAgent(), []);\n\n // Create a memoized observable for the agent's connection state\n const observable = React.useMemo(\n () => _agentConnectionStateObservable(agent),\n [agent, _agentConnectionStateObservable]\n );\n\n // Use the observable to track the agent's connection state\n const { isConnected } = useObservableState(observable, {\n isConnected: agent.isConnected,\n });\n\n // Return the connection handler and the current connection status\n return { handleConnectAgent, handleDisconnectAgent, isConnected };\n}\n\n/**\n * Sets up the connection for a voice agent.\n *\n * @returns An object containing the agent connection state observable and a function to handle agent connection.\n */\nfunction setupConnectAgent() {\n /**\n * Handles the connection of a voice agent.\n *\n * @param agent - The VoiceAgent to be connected.\n * @returns A promise that resolves when the agent is connected.\n */\n const handleConnectAgent = async (agent: VoiceAgent) => {\n await agent.connect();\n };\n\n const handleDisconnectAgent = async (agent: VoiceAgent) => {\n await agent.disconnect();\n };\n\n return {\n _agentConnectionStateObservable: agentConnectionStateObservable,\n handleConnectAgent,\n handleDisconnectAgent,\n };\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport { VoiceAgent } from \"@/rapida/agents/voice-agent\";\nimport { useEnsureVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\nimport { useObservableState } from \"@/rapida/hooks/use-observable-state\";\nimport * as React from \"react\";\nimport { agentInputObservable } from \"@/rapida/hooks/observables/voice-agent\";\nimport { Channel } from \"@/rapida/channels\";\n\nexport function useInputModeToggleAgent() {\n // ensure that voice agent is initializesd\n const agent = useEnsureVoiceAgent();\n\n /**\n *\n */\n // observe\n const { _agentInputObservable, handleTextToggle, handleVoiceToggle } =\n React.useMemo(() => toggleInputMode(), []);\n\n /**\n *\n */\n const observable = React.useMemo(\n () => _agentInputObservable(agent),\n [agent, _agentInputObservable]\n );\n\n /**\n *\n */\n const { channel } = useObservableState(observable, {\n channel: agent.inputChannel,\n });\n\n return { handleTextToggle, handleVoiceToggle, channel };\n}\n\n/**\n * Toggle input voice agent\n * @returns\n */\nfunction toggleInputMode() {\n const handleTextToggle = async (agent: VoiceAgent) => {\n // toggelling the input from audio to text\n if (agent.isTextInput) {\n return;\n }\n await agent.setInputChannel(Channel.Text);\n return;\n };\n\n const handleVoiceToggle = async (agent: VoiceAgent) => {\n // toggelling the input from audio to text\n if (agent.isAudioInput) {\n console.warn(\"already in voice mode, ignore in toggle\");\n return;\n }\n await agent.setInputChannel(Channel.Audio);\n return;\n };\n\n return {\n _agentInputObservable: agentInputObservable,\n handleVoiceToggle,\n handleTextToggle,\n };\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport * as React from \"react\";\nimport { VoiceAgent } from \"@/rapida/agents/voice-agent\";\nimport { useEnsureVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\nimport { useObservableState } from \"@/rapida/hooks/use-observable-state\";\nimport { agentAudioOutputMuteObservable } from \"@/rapida/hooks/observables/voice-agent\";\n\n/**\n * use speaker toggle agent\n * @returns\n */\nexport function useSpeakerOuputToggleAgent() {\n //\n const agent = useEnsureVoiceAgent();\n\n //\n const { _agentAudioInputMuteObservable, handleSpeakerOuputToggleAgent } =\n React.useMemo(() => speakerOuputToggleAgent(), []);\n\n const observable = React.useMemo(\n () => _agentAudioInputMuteObservable(agent),\n [agent, _agentAudioInputMuteObservable]\n );\n\n const { isEnable } = useObservableState(observable, {\n isEnable: agent.isAudioInputEnable,\n });\n\n return { handleSpeakerOuputToggleAgent, isEnable };\n}\n\n/**\n * For toggleing mic for a given agent\n * @returns\n */\nfunction speakerOuputToggleAgent() {\n const handleSpeakerOuputToggleAgent = async (agent: VoiceAgent) => {\n await agent.toggelAudioOutput();\n };\n return {\n _agentAudioInputMuteObservable: agentAudioOutputMuteObservable,\n handleSpeakerOuputToggleAgent,\n };\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nexport enum MediaDeviceFailure {\n // user rejected permissions\n PermissionDenied = \"PermissionDenied\",\n\n // device is not available\n NotFound = \"NotFound\",\n\n // device is in use. On Windows, only a single tab may get access to a device at a time.\n DeviceInUse = \"DeviceInUse\",\n\n //\n Other = \"Other\",\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport { AssistantDefinition } from \"@/rapida/clients/protos/talk-api_pb\";\nimport * as google_protobuf_any_pb from \"google-protobuf/google/protobuf/any_pb\";\nimport { StringArrayToAny, StringToAny } from \"@/rapida/utils/rapida_value\";\nimport { AssistantMessagingResponse } from \"../clients/protos/talk-api_pb\";\nimport { AgentServerEvent } from \"@/rapida/events/agent-server-event\";\nimport * as google_protobuf_struct_pb from \"google-protobuf/google/protobuf/struct_pb\";\nimport { AssistantConversationMessage } from \"@/rapida/clients/protos/common_pb\";\nimport { Channel } from \"@/rapida/channels\";\nimport { DEFAULT_DEVICE_ID } from \"@/rapida/constants\";\n\n/**\n * Callbacks for agent\n */\ninterface AgentCallback {\n onStart?: (args: google_protobuf_struct_pb.Struct | undefined) => void;\n onComplete?: (args: google_protobuf_struct_pb.Struct | undefined) => void;\n\n //\n onTranscript?: (args: google_protobuf_struct_pb.Struct | undefined) => void;\n // on interruption\n onInterrupt?: (args: google_protobuf_struct_pb.Struct | undefined) => void;\n\n // generation\n onGeneration?: (args: google_protobuf_struct_pb.Struct | undefined) => void;\n onCompleteGeneration?: (\n args: google_protobuf_struct_pb.Struct | undefined\n ) => void;\n\n // conversation callback\n onStartConversation?: (\n args: google_protobuf_struct_pb.Struct | undefined\n ) => void;\n onCompleteConversation?: (\n args: google_protobuf_struct_pb.Struct | undefined\n ) => void;\n\n // on complete message\n onMessage?: (arg: AssistantConversationMessage | undefined) => void;\n}\n\n/**\n *\n */\nexport interface PlayerOptions {\n sampleRate: number;\n}\n\n/**\n *\n */\nexport interface RecorderOptions {\n sampleRate: number;\n}\n\n/**\n *\n */\nexport class InputOptions {\n /**\n * enable channels\n */\n\n channels: Channel[] = [Channel.Audio, Channel.Text];\n\n /**\n * sample rate for player\n */\n protected recorderOptions: RecorderOptions = { sampleRate: 24000 };\n get recorderOption(): RecorderOptions {\n return this.recorderOptions;\n }\n\n /**\n * channel for providing output\n */\n protected channel: Channel = Channel.Audio;\n get defaultChannel(): Channel {\n return this.channel;\n }\n\n /**\n * device that will be ouput the media\n */\n protected deviceId?: string;\n get inputDeviceId(): string {\n return DEFAULT_DEVICE_ID;\n }\n\n /**\n *\n * @param deviceId\n */\n setDeviceId(deviceId: string) {\n this.deviceId = deviceId;\n }\n\n /**\n *\n * @param channels\n * @param channel\n * @param deviceId\n */\n constructor(channels: Channel[], channel?: Channel, deviceId?: string) {\n this.channels = channels;\n if (channel) this.channel = channel;\n this.deviceId = deviceId;\n }\n\n /**\n *\n * @param channel\n */\n changeChannel(channel: Channel) {\n this.channel = channel;\n }\n\n /**\n *\n * @param device\n */\n changeDevice(device: string) {\n this.deviceId = device;\n }\n}\nexport class OutputOptions {\n /**\n * enable channels\n */\n\n channels: Channel[] = [Channel.Audio, Channel.Text];\n\n /**\n * sample rate for player\n */\n protected playerOptions: PlayerOptions = { sampleRate: 24000 };\n get playerOption(): PlayerOptions {\n return this.playerOptions;\n }\n\n /**\n * channel for providing output\n */\n protected channel: Channel = Channel.Audio;\n get defaultChannel(): Channel {\n return this.channel;\n }\n\n /**\n * device that will be ouput the media\n */\n protected deviceId?: string;\n get outputDeviceId(): string {\n return DEFAULT_DEVICE_ID;\n }\n\n /**\n *\n * @param channels\n * @param channel\n * @param deviceId\n */\n constructor(channels: Channel[], channel?: Channel, deviceId?: string) {\n this.channels = channels;\n if (channel) this.channel = channel;\n this.deviceId = deviceId;\n }\n\n /**\n *\n * @param channel\n */\n changeChannel(channel: Channel) {\n this.channel = channel;\n }\n\n /**\n *\n * @param deviceId\n */\n setDeviceId(deviceId: string) {\n this.deviceId = deviceId;\n }\n\n /**\n *\n * @param device\n */\n changeDevice(device: string) {\n this.deviceId = device;\n }\n}\n/**\n * Represents the configuration settings for an agent.\n * This includes the agent's unique identifier, version, and configurable arguments.\n */\nexport class AgentConfig {\n /**\n * Unique identifier for the agent.\n */\n id: string;\n\n /**\n * (Optional) Version number of the agent.\n */\n version?: string;\n\n /**\n * arguments for assistant\n */\n arguments?: Map<string, google_protobuf_any_pb.Any>;\n\n /**\n * options for assistants\n */\n options?: Map<string, google_protobuf_any_pb.Any>;\n\n /**\n * metadata for assistant request\n */\n metadata?: Map<string, google_protobuf_any_pb.Any>;\n\n /**\n * all the agent callback\n */\n callbacks?: AgentCallback;\n\n /**\n *\n */\n inputOptions: InputOptions;\n\n /**\n *\n */\n outputOptions: OutputOptions;\n\n /**\n * Initializes a new instance of `AgentConfig`.\n *\n * @param id - Unique identifier for the agent.\n * @param version - (Optional) Version number of the agent.\n * @param argument - (Optional) Configuration arguments for the agent.\n */\n constructor(\n id: string,\n inputOptions: InputOptions = new InputOptions([\n Channel.Audio,\n Channel.Text,\n ]),\n outputOptions: OutputOptions = new OutputOptions([\n Channel.Audio,\n Channel.Text,\n ]),\n version?: string,\n argument?: Map<string, google_protobuf_any_pb.Any>,\n options?: Map<string, google_protobuf_any_pb.Any>,\n metadata?: Map<string, google_protobuf_any_pb.Any>\n ) {\n this.id = id;\n this.version = version;\n this.arguments = argument;\n this.options = options;\n this.metadata = metadata;\n this.inputOptions = inputOptions;\n this.outputOptions = outputOptions;\n }\n\n /**\n * Retrieves the assistant definition for this agent.\n *\n * @returns {AssistantDefinition} A configured `AssistantDefinition` instance with the agent's details.\n */\n get definition(): AssistantDefinition {\n const def = new AssistantDefinition();\n def.setAssistantid(this.id); // Sets the agent's unique ID.\n\n if (this.version) {\n def.setVersion(this.version); // Sets the agent's version if provided.\n }\n\n return def;\n }\n\n /**\n * for adding custom dictionary\n * it allows user to add custom keywords to given agent it will perform correction\n * @param keywords\n */\n addKeywords(keywords: string[]): this {\n if (this.options == undefined) this.options = new Map();\n this.options[\"keywords\"] = StringArrayToAny(keywords);\n return this;\n }\n\n /**\n * Want to add other options to override\n * @param k\n * @param otp\n * @returns\n */\n addCustomOption(k: string, otp: google_protobuf_any_pb.Any): this {\n if (this.options == undefined) this.options = new Map();\n this.options.set(k, otp);\n return this;\n }\n\n /**\n *\n * @param k\n * @param meta\n * @returns\n */\n addMetadata(k: string, meta: google_protobuf_any_pb.Any): this {\n if (this.metadata == undefined) this.metadata = new Map();\n this.metadata.set(k, meta);\n return this;\n }\n\n /**\n *\n * @param k\n * @param value\n * @returns\n */\n addArgument(k: string, value: string): this {\n if (this.arguments == undefined) this.arguments = new Map();\n this.arguments?.set(k, StringToAny(value));\n return this;\n }\n\n /**\n * Sets up callback functions for various events in the agent's conversation lifecycle.\n *\n * @param onStartConversation - Callback function triggered when a conversation starts.\n * @param onInterruption - Callback function triggered when the conversation is interrupted.\n * @param onListen - Callback function triggered when the agent starts listening.\n * @param onComplete - Callback function triggered when a specific action or process is completed.\n * @param onReceiveTranscript - Callback function triggered when a transcript is received.\n * @param onReceive - Callback function triggered when any message is received.\n * @param onSendGeneration - Callback function triggered before sending a generated response.\n * @param onCompleteGeneration - Callback function triggered after completing the generation of a response.\n * @param onCompleteConversation - Callback function triggered when the entire conversation is completed.\n * @returns The current instance of the AgentConfig, allowing for method chaining.\n */\n withAgentCallback(cl: AgentCallback): this {\n this.callbacks = cl;\n return this;\n }\n\n /**\n *\n * @param response\n * @returns\n */\n onCallback(response: AssistantMessagingResponse): void {\n // check if callback is register then call it off\n switch (response.getDataCase()) {\n case AssistantMessagingResponse.DataCase.DATA_NOT_SET:\n break;\n case AssistantMessagingResponse.DataCase.EVENT:\n if (response.getEvent()) {\n switch (response.getEvent()?.getName()) {\n case AgentServerEvent.Transcript:\n if (this.callbacks && this.callbacks?.onTranscript) {\n this.callbacks.onTranscript(response.getEvent()?.getMeta());\n }\n break;\n case AgentServerEvent.Interruption:\n if (this.callbacks && this.callbacks?.onInterrupt) {\n this.callbacks.onInterrupt(response.getEvent()?.getMeta());\n }\n break;\n case AgentServerEvent.Generation:\n if (this.callbacks && this.callbacks?.onGeneration) {\n this.callbacks.onGeneration(response.getEvent()?.getMeta());\n }\n break;\n case AgentServerEvent.CompleteConversation:\n if (this.callbacks && this.callbacks?.onCompleteConversation) {\n this.callbacks.onCompleteConversation(\n response.getEvent()?.getMeta()\n );\n }\n break;\n case AgentServerEvent.Complete:\n if (this.callbacks && this.callbacks?.onComplete) {\n this.callbacks.onComplete(response.getEvent()?.getMeta());\n }\n break;\n case AgentServerEvent.CompleteGeneration:\n if (this.callbacks && this.callbacks?.onCompleteGeneration) {\n this.callbacks.onCompleteGeneration(\n response.getEvent()?.getMeta()\n );\n }\n break;\n case AgentServerEvent.Start:\n if (this.callbacks && this.callbacks?.onStart) {\n this.callbacks.onStart(response.getEvent()?.getMeta());\n }\n break;\n case AgentServerEvent.StartConversation:\n if (this.callbacks && this.callbacks?.onStartConversation) {\n this.callbacks.onStartConversation(\n response.getEvent()?.getMeta()\n );\n }\n break;\n }\n }\n return;\n case AssistantMessagingResponse.DataCase.MESSAGE:\n if (this.callbacks && this.callbacks?.onMessage) {\n this.callbacks.onMessage(response.getMessage());\n }\n break;\n default:\n break;\n }\n }\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport { Map } from \"google-protobuf\";\nimport { Any } from \"google-protobuf/google/protobuf/any_pb\";\nimport * as google_protobuf_any_pb from \"google-protobuf/google/protobuf/any_pb\";\nimport {\n StringValue,\n Int32Value,\n DoubleValue,\n BoolValue,\n BytesValue,\n} from \"google-protobuf/google/protobuf/wrappers_pb\";\n\n// Helper function to pack data into `Any`\nfunction pack(serialized: Uint8Array, typeUrlPrefix: string): Any {\n const anyValue = new Any();\n anyValue.pack(serialized, typeUrlPrefix);\n return anyValue;\n}\n\n// string array to `Any`\nexport function StringArrayToAny(values: string[]): Any {\n return values.map((x) => {\n return StringToAny(x);\n });\n}\n\n// string to `Any`\nexport function StringToAny(value: string): Any {\n const stringValue = new StringValue();\n stringValue.setValue(value);\n const serialized = stringValue.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.StringValue\");\n}\n\n// `Any` to string\nexport function AnyToString(anyValue: Any): string {\n const stringValue = StringValue.deserializeBinary(anyValue.getValue_asU8());\n return stringValue.getValue();\n}\n\n// float to `Any`\nexport function FloatToAny(value: number): Any {\n const doubleValue = new DoubleValue();\n doubleValue.setValue(value);\n const serialized = doubleValue.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.DoubleValue\");\n}\n\n// `Any` to float\nexport function AnyToFloat(anyValue: Any): number {\n const doubleValue = DoubleValue.deserializeBinary(anyValue.getValue_asU8());\n return doubleValue.getValue();\n}\n\n// integer to `Any`\nexport function Int32ToAny(value: number): Any {\n const int32Value = new Int32Value();\n int32Value.setValue(value);\n const serialized = int32Value.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.Int32Value\");\n}\n\n// `Any` to integer\nexport function AnyToInt32(anyValue: Any): number {\n const int32Value = Int32Value.deserializeBinary(anyValue.getValue_asU8());\n return int32Value.getValue();\n}\n\n// boolean to `Any`\nexport function BoolToAny(value: boolean): Any {\n const boolValue = new BoolValue();\n boolValue.setValue(value);\n const serialized = boolValue.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.BoolValue\");\n}\n\n// `Any` to boolean\nexport function AnyToBool(anyValue: Any): boolean {\n const boolValue = BoolValue.deserializeBinary(anyValue.getValue_asU8());\n return boolValue.getValue();\n}\n\n// bytes to `Any`\nexport function BytesToAny(value: Uint8Array): Any {\n const bytesValue = new BytesValue();\n bytesValue.setValue(value);\n const serialized = bytesValue.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.BytesValue\");\n}\n\n// JSON object to `Any`\nexport function JSONToAny(value: object): Any {\n const jsonString = JSON.stringify(value);\n const stringValue = new StringValue();\n stringValue.setValue(jsonString);\n const serialized = stringValue.serializeBinary();\n return pack(serialized, \"type.googleapis.com/google.protobuf.StringValue\");\n}\n\n// `Any` to JSON object\nexport function AnyToJSON(anyValue: Any): object {\n const stringValue = StringValue.deserializeBinary(anyValue.getValue_asU8());\n return JSON.parse(stringValue.getValue());\n}\n\nexport function MapToObject(\n protoMap: Map<string, google_protobuf_any_pb.Any>\n): Record<string, any> {\n const result: Record<string, any> = {};\n protoMap.forEach((value, key) => {\n try {\n if (value instanceof Any) {\n // Get the underlying message as a plain object\n result[key] = new TextDecoder().decode(value.getValue_asU8());\n } else {\n result[key] = value;\n }\n } catch (x) {}\n });\n\n return result;\n}\n","/*\n * Copyright (c) 2024. Rapida\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n *\n * Author: Prashant <prashant@rapida.ai>\n *\n */\nimport { useState, useEffect, useRef } from \"react\";\nimport { useMaybeVoiceAgent } from \"@/rapida/hooks/use-voice-agent\";\n\n/**\n * The function `useMultibandSpeakerTrackVolume` calculates and updates frequency bands based on audio\n * player data at regular intervals.\n * @param {number} [bands=5] - The `bands` parameter in the `useMultibandSpeakerTrackVolume` function\n * determines the number of frequency bands that the audio signal will be divided into for processing.\n * This parameter allows you to specify how many bands you want to split the frequency spectrum into.\n * @param {number} [loPass=0.1] - The `loPass` parameter in the `useMultibandSpeakerTrackVolume`\n * function represents the lower passband frequency limit. It is used to filter out frequencies below a\n * certain threshold in the audio signal. In the provided code snippet, the `loPass` parameter is set\n * to a default value of\n * @param {number} [hiPass=1.0] - The `hiPass` parameter in the `useMultibandSpeakerTrackVolume`\n * function represents the upper limit frequency value for filtering the audio frequencies. It is used\n * to define the range of frequencies that will be included in the analysis and processing of the audio\n * data. In the provided code snippet, the `\n * @returns The `useMultibandSpeakerTrackVolume` custom hook returns an array of `Float32Array`\n * frequency bands that have been calculated based on the audio frequencies received from the audio\n * player. The number of bands, low-pass and high-pass values are used to divide and normalize the\n * frequencies into the specified bands. The hook continuously updates the frequency bands at an\n * interval of 100ms.\n */\n\nexport const useMultibandMicrophoneTrackVolume = (\n bands: number = 5,\n loPass: number = 0.1,\n hiPass: number = 1.0\n) => {\n const [frequencyBands, setFrequencyBands] = useState<number[][]>(\n Array(bands)\n .fill([])\n .map(() => Array(32).fill(0))\n );\n const agentContext = useMaybeVoiceAgent();\n\n useEffect(() => {\n const updateVolume = () => {\n if (!agentContext || agentContext.recorder.getStatus() !== \"recording\") {\n return;\n }\n\n const frequencies =\n agentContext.recorder.getFrequencies(\"frequency\")?.values;\n if (!frequencies || frequencies.length === 0) return;\n\n // Calculate the frequency range we want to analyze\n const startIndex = Math.floor(frequencies.length * loPass);\n const endIndex = Math.floor(frequencies.length * hiPass);\n const usableFrequencies = Array.from(\n frequencies.slice(startIndex, endIndex)\n );\n\n // Split frequencies into bands\n const samplesPerBand = Math.floor(usableFrequencies.length / bands);\n const bandArrays: number[][] = [];\n\n for (let bandIndex = 0; bandIndex < bands; bandIndex++) {\n const bandStart = bandIndex * samplesPerBand;\n const bandEnd =\n bandIndex === bands - 1\n ? usableFrequencies.length\n : (bandIndex + 1) * samplesPerBand;\n\n // Get frequencies for this band and normalize them\n const bandFrequencies = usableFrequencies\n .slice(bandStart, bandEnd)\n .map((amplitude) => {\n // Ensure amplitude is treated as a number\n const numericAmplitude = Number(amplitude);\n // Normalize amplitude to 0-1 range\n return Math.min(1, Math.max(0, numericAmplitude * 4));\n });\n\n // Ensure we have a consistent number of samples per band\n const resampledBand = resampleArray(bandFrequencies, 32); // 32 samples per band\n bandArrays.push(resampledBand);\n }\n\n // Apply smoothing to prevent jarring transitions\n setFrequencyBands((prevBands) => {\n if (prevBands.length !== bands) return bandArrays;\n\n return bandArrays.map((bandFrequencies, bandIndex) => {\n if (!prevBands[bandIndex]) return bandFrequencies;\n\n const smoothingFactor = 0.7;\n return bandFrequencies.map((freq, i) => {\n const prevValue = prevBands[bandIndex][i] || 0;\n return freq * smoothingFactor + prevValue * (1 - smoothingFactor);\n });\n });\n });\n };\n\n // Helper function to resample array to desired length\n const resampleArray = (arr: number[], newLength: number): number[] => {\n const result = new Array(newLength);\n const stepSize = arr.length / newLength;\n\n for (let i = 0; i < newLength; i++) {\n const start = Math.floor(i * stepSize);\n const end = Math.floor((i + 1) * stepSize);\n let sum = 0;\n\n for (let j = start; j < end; j++) {\n sum += arr[j] || 0;\n }\n\n result[i] = sum / (end - start);\n }\n\n return result;\n };\n\n const interval = setInterval(updateVolume, 100);\n return () => clearInterval(interval);\n }, [agentContext, loPass, hiPass, bands]);\n\n return frequencyBands;\n};\n\nexport const useMultibandSpeakerTrackVolume = (\n bands: number = 5,\n loPass: number = 0.1,\n hiPass: number = 1.0\n) => {\n const [frequencyBands, setFrequencyBands] = useState<number[][]>(\n Array(bands)\n .fill([])\n .map(() => Array(32).fill(0))\n );\n const agentContext = useMaybeVoiceAgent();\n\n useEffect(() => {\n const updateVolume = () => {\n if (!agentContext?.player?.analyser) {\n return;\n }\n\n const frequencies =\n agentContext.player.getFrequencies(\"frequency\")?.values;\n if (!frequencies || frequencies.length === 0) return;\n\n // Calculate the frequency range we want to analyze\n const startIndex = Math.floor(fre