Add voice + speed to indexedDB and config context

This commit is contained in:
Richard Roberson 2025-01-26 18:39:11 -07:00
parent 55919310a5
commit e830957598
3 changed files with 30 additions and 9 deletions

View file

@ -5,6 +5,7 @@ import {
ListboxOptions, ListboxOptions,
} from '@headlessui/react'; } from '@headlessui/react';
import { ChevronUpDownIcon } from '@/components/icons/Icons'; import { ChevronUpDownIcon } from '@/components/icons/Icons';
import { useConfig } from '@/contexts/ConfigContext';
const speedOptions = [ const speedOptions = [
{ value: 0.5, label: '0.5x' }, { value: 0.5, label: '0.5x' },
@ -22,11 +23,16 @@ export const SpeedControl = ({ speed, setSpeedAndRestart }: {
speed: number; speed: number;
setSpeedAndRestart: (speed: number) => void; setSpeedAndRestart: (speed: number) => void;
}) => { }) => {
const { voiceSpeed } = useConfig();
// Use voiceSpeed as the source of truth
const currentSpeed = voiceSpeed;
return ( return (
<div className="relative"> <div className="relative">
<Listbox value={speed} onChange={setSpeedAndRestart}> <Listbox value={currentSpeed} onChange={setSpeedAndRestart}>
<ListboxButton className="flex items-center space-x-1 bg-transparent text-foreground text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-2 pr-1 py-1"> <ListboxButton className="flex items-center space-x-1 bg-transparent text-foreground text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-2 pr-1 py-1">
<span>{speed}x</span> <span>{currentSpeed}x</span>
<ChevronUpDownIcon className="h-3 w-3" /> <ChevronUpDownIcon className="h-3 w-3" />
</ListboxButton> </ListboxButton>
<ListboxOptions className="absolute bottom-full mb-1 w-24 overflow-auto rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> <ListboxOptions className="absolute bottom-full mb-1 w-24 overflow-auto rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">

View file

@ -5,17 +5,23 @@ import {
ListboxOptions, ListboxOptions,
} from '@headlessui/react'; } from '@headlessui/react';
import { ChevronUpDownIcon } from '@/components/icons/Icons'; import { ChevronUpDownIcon } from '@/components/icons/Icons';
import { useConfig } from '@/contexts/ConfigContext';
export const VoicesControl = ({ voice, availableVoices, setVoiceAndRestart }: { export const VoicesControl = ({ voice, availableVoices, setVoiceAndRestart }: {
voice: string; voice: string;
availableVoices: string[]; availableVoices: string[];
setVoiceAndRestart: (voice: string) => void; setVoiceAndRestart: (voice: string) => void;
}) => { }) => {
const { voice: configVoice } = useConfig();
// Use configVoice as the source of truth
const currentVoice = configVoice;
return ( return (
<div className="relative"> <div className="relative">
<Listbox value={voice} onChange={setVoiceAndRestart}> <Listbox value={currentVoice} onChange={setVoiceAndRestart}>
<ListboxButton className="flex items-center space-x-1 bg-transparent text-foreground text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-2 pr-1 py-1"> <ListboxButton className="flex items-center space-x-1 bg-transparent text-foreground text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-2 pr-1 py-1">
<span>{voice}</span> <span>{currentVoice}</span>
<ChevronUpDownIcon className="h-3 w-3" /> <ChevronUpDownIcon className="h-3 w-3" />
</ListboxButton> </ListboxButton>
<ListboxOptions className="absolute bottom-full mb-1 w-32 overflow-auto rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> <ListboxOptions className="absolute bottom-full mb-1 w-32 overflow-auto rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">

View file

@ -61,7 +61,14 @@ interface TTSContextType {
const TTSContext = createContext<TTSContextType | undefined>(undefined); const TTSContext = createContext<TTSContextType | undefined>(undefined);
export function TTSProvider({ children }: { children: React.ReactNode }) { export function TTSProvider({ children }: { children: React.ReactNode }) {
const { apiKey: openApiKey, baseUrl: openApiBaseUrl, isLoading: configIsLoading } = useConfig(); const {
apiKey: openApiKey,
baseUrl: openApiBaseUrl,
isLoading: configIsLoading,
voiceSpeed,
voice: configVoice,
updateConfigKey
} = useConfig();
// Move openai initialization to a ref to avoid breaking hooks rules // Move openai initialization to a ref to avoid breaking hooks rules
const openaiRef = useRef<OpenAI | null>(null); const openaiRef = useRef<OpenAI | null>(null);
@ -75,8 +82,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const [activeHowl, setActiveHowl] = useState<Howl | null>(null); const [activeHowl, setActiveHowl] = useState<Howl | null>(null);
const [audioQueue] = useState<AudioBuffer[]>([]); const [audioQueue] = useState<AudioBuffer[]>([]);
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [speed, setSpeed] = useState(1); const [speed, setSpeed] = useState(voiceSpeed);
const [voice, setVoice] = useState('alloy'); const [voice, setVoice] = useState(configVoice);
const [availableVoices, setAvailableVoices] = useState<string[]>([]); const [availableVoices, setAvailableVoices] = useState<string[]>([]);
const [currDocPage, setCurrDocPage] = useState<number>(1); const [currDocPage, setCurrDocPage] = useState<number>(1);
@ -422,6 +429,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
const setSpeedAndRestart = useCallback((newSpeed: number) => { const setSpeedAndRestart = useCallback((newSpeed: number) => {
setSpeed(newSpeed); setSpeed(newSpeed);
updateConfigKey('voiceSpeed', newSpeed);
// Clear the audio cache since it contains audio at the old speed // Clear the audio cache since it contains audio at the old speed
audioCacheRef.current.clear(); audioCacheRef.current.clear();
@ -431,10 +439,11 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setCurrentIndex(currentIdx); setCurrentIndex(currentIdx);
setIsPlaying(true); setIsPlaying(true);
} }
}, [isPlaying, currentIndex, stop]); }, [isPlaying, currentIndex, stop, updateConfigKey]);
const setVoiceAndRestart = useCallback((newVoice: string) => { const setVoiceAndRestart = useCallback((newVoice: string) => {
setVoice(newVoice); setVoice(newVoice);
updateConfigKey('voice', newVoice);
// Clear the audio cache since it contains audio with the old voice // Clear the audio cache since it contains audio with the old voice
audioCacheRef.current.clear(); audioCacheRef.current.clear();
@ -444,7 +453,7 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
setCurrentIndex(currentIdx); setCurrentIndex(currentIdx);
setIsPlaying(true); setIsPlaying(true);
} }
}, [isPlaying, currentIndex, stop]); }, [isPlaying, currentIndex, stop, updateConfigKey]);
const value = { const value = {
isPlaying, isPlaying,