Add voice + speed to indexedDB and config context
This commit is contained in:
parent
55919310a5
commit
e830957598
3 changed files with 30 additions and 9 deletions
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue