persist tts settings

This commit is contained in:
legop3
2025-12-25 21:27:19 -05:00
parent 8e1de78cc7
commit 6301058f87
4 changed files with 45 additions and 19 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -11,7 +11,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-DoVsLZ1w.js"></script> <script type="module" crossorigin src="/assets/index-Bfcf31QB.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B88Ix9u6.css"> <link rel="stylesheet" crossorigin href="/assets/index-B88Ix9u6.css">
</head> </head>
<body> <body>
+32 -6
View File
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useChat } from '../context/ChatContext.jsx'; import { useChat } from '../context/ChatContext.jsx';
import { useSession } from '../context/SessionContext.jsx'; import { useSession } from '../context/SessionContext.jsx';
import { useSettingsNamespace } from '../settings/index.js';
function roleColors(role, fromDiscord) { function roleColors(role, fromDiscord) {
if (fromDiscord) return 'text-indigo-200'; if (fromDiscord) return 'text-indigo-200';
@@ -31,12 +32,16 @@ const ESPEAK_PITCHES = Array.from({ length: 10 }, (_, idx) => idx * 10);
export default function ChatPanel({ hideInput = false, hideSpectatorNotice = false, fillHeight = false }) { export default function ChatPanel({ hideInput = false, hideSpectatorNotice = false, fillHeight = false }) {
const { session } = useSession(); const { session } = useSession();
const { messages, sendMessage, registerInputRef, onInputFocus, onInputBlur, blurChat } = useChat(); const { messages, sendMessage, registerInputRef, onInputFocus, onInputBlur, blurChat } = useChat();
const {
value: ttsSettings,
save: saveTtsSettings,
} = useSettingsNamespace('tts', { engine: 'flite', voice: 'rms', pitch: 50 });
const [draft, setDraft] = useState(''); const [draft, setDraft] = useState('');
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [speak, setSpeak] = useState(false); const [speak, setSpeak] = useState(false);
const [engine, setEngine] = useState('flite'); const [engine, setEngine] = useState(() => ttsSettings?.engine || 'flite');
const [voice, setVoice] = useState('rms'); const [voice, setVoice] = useState(() => ttsSettings?.voice || 'rms');
const [pitch, setPitch] = useState(50); const [pitch, setPitch] = useState(() => (Number.isFinite(ttsSettings?.pitch) ? ttsSettings.pitch : 50));
const canChat = session?.role !== 'spectator'; const canChat = session?.role !== 'spectator';
const listRef = useRef(null); const listRef = useRef(null);
const currentRoverId = session?.assignment?.roverId || null; const currentRoverId = session?.assignment?.roverId || null;
@@ -54,6 +59,15 @@ export default function ChatPanel({ hideInput = false, hideSpectatorNotice = fal
listRef.current.scrollTop = listRef.current.scrollHeight; listRef.current.scrollTop = listRef.current.scrollHeight;
}, [sorted]); }, [sorted]);
useEffect(() => {
const nextEngine = ttsSettings?.engine || 'flite';
const nextVoice = ttsSettings?.voice || 'rms';
const nextPitch = Number.isFinite(ttsSettings?.pitch) ? ttsSettings.pitch : 50;
if (engine !== nextEngine) setEngine(nextEngine);
if (voice !== nextVoice) setVoice(nextVoice);
if (pitch !== nextPitch) setPitch(nextPitch);
}, [engine, pitch, ttsSettings?.engine, ttsSettings?.pitch, ttsSettings?.voice, voice]);
useEffect(() => { useEffect(() => {
if (ttsSupported) { if (ttsSupported) {
setSpeak(true); setSpeak(true);
@@ -154,7 +168,11 @@ export default function ChatPanel({ hideInput = false, hideSpectatorNotice = fal
</label> </label>
<select <select
value={engine} value={engine}
onChange={(e) => setEngine(e.target.value)} onChange={(e) => {
const next = e.target.value;
setEngine(next);
saveTtsSettings((current) => ({ ...(current || {}), engine: next }));
}}
className="field-input text-xs" className="field-input text-xs"
> >
<option value="flite">flite</option> <option value="flite">flite</option>
@@ -163,7 +181,11 @@ export default function ChatPanel({ hideInput = false, hideSpectatorNotice = fal
{engine === 'flite' ? ( {engine === 'flite' ? (
<select <select
value={voice} value={voice}
onChange={(e) => setVoice(e.target.value)} onChange={(e) => {
const next = e.target.value;
setVoice(next);
saveTtsSettings((current) => ({ ...(current || {}), voice: next }));
}}
className="field-input text-xs" className="field-input text-xs"
> >
{FLITE_VOICES.map((v) => ( {FLITE_VOICES.map((v) => (
@@ -175,7 +197,11 @@ export default function ChatPanel({ hideInput = false, hideSpectatorNotice = fal
) : ( ) : (
<select <select
value={pitch} value={pitch}
onChange={(e) => setPitch(Number(e.target.value))} onChange={(e) => {
const next = Number(e.target.value);
setPitch(next);
saveTtsSettings((current) => ({ ...(current || {}), pitch: next }));
}}
className="field-input text-xs" className="field-input text-xs"
> >
{ESPEAK_PITCHES.map((p) => ( {ESPEAK_PITCHES.map((p) => (