Spaces:
Running
Running
| import { Select, Stack, Text } from "@mantine/core"; | |
| import { useForm } from "@mantine/form"; | |
| import getUnicodeFlagIcon from "country-flag-icons/unicode"; | |
| import { usePubSub } from "create-pubsub/react"; | |
| import { useCallback, useEffect, useState } from "react"; | |
| import { settingsPubSub } from "../../../../modules/pubSub"; | |
| export default function VoiceSettingsForm() { | |
| const [settings, setSettings] = usePubSub(settingsPubSub); | |
| const [voices, setVoices] = useState<{ value: string; label: string }[]>([]); | |
| const getCountryFlag = useCallback((langCode: string) => { | |
| try { | |
| const country = langCode.split("-")[1]; | |
| if (country.length !== 2) throw new Error("Invalid country code"); | |
| return getUnicodeFlagIcon(country); | |
| } catch { | |
| return "π"; | |
| } | |
| }, []); | |
| const form = useForm({ | |
| initialValues: settings, | |
| onValuesChange: setSettings, | |
| }); | |
| useEffect(() => { | |
| const updateVoices = () => { | |
| const availableVoices = self.speechSynthesis.getVoices(); | |
| const uniqueVoices = Array.from( | |
| new Map( | |
| availableVoices.map((voice) => [voice.voiceURI, voice]), | |
| ).values(), | |
| ); | |
| const voiceOptions = uniqueVoices | |
| .sort((a, b) => a.lang.localeCompare(b.lang)) | |
| .map((voice) => ({ | |
| value: voice.voiceURI, | |
| label: `${getCountryFlag(voice.lang)} ${voice.name} β’ ${voice.lang}`, | |
| })); | |
| setVoices(voiceOptions); | |
| }; | |
| updateVoices(); | |
| self.speechSynthesis.onvoiceschanged = updateVoices; | |
| return () => { | |
| self.speechSynthesis.onvoiceschanged = null; | |
| }; | |
| }, [getCountryFlag]); | |
| return ( | |
| <Stack gap="xs"> | |
| <Text size="sm">Voice Selection</Text> | |
| <Text size="xs" c="dimmed"> | |
| Choose the voice to use when reading AI responses aloud. | |
| </Text> | |
| <Select | |
| {...form.getInputProps("selectedVoiceId")} | |
| data={voices} | |
| searchable | |
| nothingFoundMessage="No voices found" | |
| placeholder="Auto-detected" | |
| allowDeselect={true} | |
| clearable | |
| /> | |
| </Stack> | |
| ); | |
| } | |