fix(player): prevent selection reset during voice refetch by deriving voices from props
Remove internal selectedVoices state and use a memoized value based on the voice prop to determine the selected voices. Avoid side effects on selection display, ensuring that voice selection persists even when available voices are refetched. This addresses issues where transient backend states could cause unwanted resets to the first voice option.
This commit is contained in:
parent
379e5082f0
commit
f7c93c90a6
1 changed files with 12 additions and 13 deletions
|
|
@ -4,7 +4,7 @@ import {
|
|||
Listbox,
|
||||
} from '@headlessui/react';
|
||||
import { ChevronUpDownIcon, AudioWaveIcon, CheckIcon } from '@/components/icons/Icons';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { buildKokoroVoiceString, parseKokoroVoiceNames } from '@/lib/shared/kokoro';
|
||||
import { keepKokoroVoicesInOneLanguage } from '@/lib/shared/language';
|
||||
import { type TtsProviderType } from '@/lib/shared/tts-provider-catalog';
|
||||
|
|
@ -56,10 +56,15 @@ export function VoicesControlBase({
|
|||
const isKokoro = providerModelPolicy.isKokoroModel;
|
||||
const maxVoices = providerModelPolicy.maxVoices;
|
||||
|
||||
const [selectedVoices, setSelectedVoices] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!(isKokoro && maxVoices > 1)) return;
|
||||
// Selected voices are derived from the `voice` prop for display only. The
|
||||
// control is fully controlled: it never writes back to config as a side
|
||||
// effect, only in direct response to a user selection (see onChange below).
|
||||
// A write-back effect here would re-fire during the availableVoices refetch
|
||||
// churn that a voice change triggers and, under a slow/contended backend,
|
||||
// push a transiently-collapsed selection back as the voice — resetting it to
|
||||
// the first option.
|
||||
const selectedVoices = useMemo(() => {
|
||||
if (!(isKokoro && maxVoices > 1)) return [];
|
||||
let initial: string[] = [];
|
||||
if (voice && voice.includes('+')) {
|
||||
initial = parseKokoroVoiceNames(voice);
|
||||
|
|
@ -71,13 +76,8 @@ export function VoicesControlBase({
|
|||
if (initial.length > maxVoices) {
|
||||
initial = initial.slice(0, maxVoices);
|
||||
}
|
||||
initial = keepKokoroVoicesInOneLanguage(initial);
|
||||
setSelectedVoices(initial);
|
||||
const combined = buildKokoroVoiceString(initial);
|
||||
if (combined && combined !== voice) {
|
||||
onChangeVoice(combined);
|
||||
}
|
||||
}, [isKokoro, maxVoices, voice, availableVoices, onChangeVoice]);
|
||||
return keepKokoroVoicesInOneLanguage(initial);
|
||||
}, [isKokoro, maxVoices, voice, availableVoices]);
|
||||
|
||||
const currentVoice = useMemo(() => {
|
||||
if (isKokoro && maxVoices > 1) {
|
||||
|
|
@ -123,7 +123,6 @@ export function VoicesControlBase({
|
|||
}
|
||||
}
|
||||
|
||||
setSelectedVoices(next);
|
||||
const combined = buildKokoroVoiceString(next);
|
||||
if (combined) {
|
||||
onChangeVoice(combined);
|
||||
|
|
|
|||
Loading…
Reference in a new issue