This commit is contained in:
legop3
2026-07-12 17:51:54 -04:00
parent e254eea9e4
commit 30b8867b3a
15 changed files with 700 additions and 25 deletions
+23 -3
View File
@@ -49,17 +49,37 @@ function resolveTtsSettings(settings) {
function useChatComposerSessionState(allowSpectatorInput) {
const role = useSessionSelector((state) => state.session?.role || null);
const socketId = useSessionSelector((state) => state.session?.socketId || null);
const currentRoverId = useSessionSelector((state) => state.session?.assignment?.roverId || null);
const users = useSessionSelector((state) => state.session?.users ?? []);
const roster = useSessionSelector((state) => state.session?.roster ?? []);
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
const chatTargetId = useMemo(() => {
/*
PTZ is intentionally not a roster entry, but session users expose the
current chat target for each socket. Prefer the self user entry so the TTS
control follows PTZ queue/operation state instead of only physical rover
assignment state.
*/
const self = users.find((entry) => entry?.socketId === socketId);
return self?.roverId || currentRoverId || null;
}, [currentRoverId, socketId, users]);
const rover = useMemo(
() => roster.find((entry) => String(entry.id) === String(currentRoverId)) || null,
[currentRoverId, roster],
() => roster.find((entry) => String(entry.id) === String(chatTargetId)) || null,
[chatTargetId, roster],
);
const ptzTtsSupported = Boolean(
ptz?.id &&
String(chatTargetId) === String(ptz.id) &&
ptz?.audio?.enabled &&
(ptz?.isOperator || ptz?.queuedPosition),
);
return {
canChat: role !== 'spectator' || allowSpectatorInput,
ttsSupported: Boolean(rover?.audio?.ttsEnabled),
ttsSupported: Boolean(rover?.audio?.ttsEnabled || ptzTtsSupported),
};
}
@@ -24,8 +24,11 @@ function detectSafari() {
function HudChatInput({ compact = false }) {
const role = useSessionSelector((state) => state.session?.role || null);
const socketId = useSessionSelector((state) => state.session?.socketId || null);
const currentRoverId = useSessionSelector((state) => state.session?.assignment?.roverId || null);
const users = useSessionSelector((state) => state.session?.users || []);
const roverRoster = useSessionSelector((state) => state.session?.roster || []);
const ptz = useSessionSelector((state) => state.session?.ptzCamera || null);
const { sendMessage, onInputFocus, onInputBlur, blurChat, registerInputRef, setTypingActive } = useChatActions();
const { value: ttsSettings } = useSettingsNamespace('tts', {
// HUD chat shares the normal browser TTS defaults, but it has no visible
@@ -41,11 +44,27 @@ function HudChatInput({ compact = false }) {
const [sending, setSending] = useState(false);
const canChat = role !== 'spectator';
const hideHudChat = role === 'spectator';
const chatTargetId = useMemo(() => {
/*
HUD chat does not render the full composer controls, but it still sends
TTS payloads when the active target supports speech. PTZ appears only in
the session user target, not the physical rover roster, so use the same
self-target resolution as the full chat panel.
*/
const self = users.find((entry) => entry?.socketId === socketId);
return self?.roverId || currentRoverId || null;
}, [currentRoverId, socketId, users]);
const rover = useMemo(
() => roverRoster.find((entry) => String(entry.id) === String(currentRoverId)) || null,
[currentRoverId, roverRoster],
() => roverRoster.find((entry) => String(entry.id) === String(chatTargetId)) || null,
[chatTargetId, roverRoster],
);
const ttsSupported = Boolean(rover?.audio?.ttsEnabled);
const ptzTtsSupported = Boolean(
ptz?.id &&
String(chatTargetId) === String(ptz.id) &&
ptz?.audio?.enabled &&
(ptz?.isOperator || ptz?.queuedPosition),
);
const ttsSupported = Boolean(rover?.audio?.ttsEnabled || ptzTtsSupported);
const ttsPayload = useMemo(() => {
if (!ttsSupported) return null;
const engine =