Skip to content

Commit fde32b0

Browse files
HugoGresseclaude
andauthored
Move to next talk on the STT automatically (#276)
* Move to next talk on the STT automatically * fix(transcription): allow endpointing 0 + prod webhook logging Address Copilot review on #275: - endpointing settings field & URL param now accept 0 (disable endpointing); previously the falsy `|| fallback` and the num() `> 0` guard made 0 impossible to set. - move GO-reminder scheduling error from request.log to console.* so it reaches Cloud Logging in production (Fastify logger is disabled outside dev/test). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(transcription): avoid double auto-advance under StrictMode Address Copilot review on #276: calling onAdvance() synchronously for an already-ended talk fired twice under React 18 StrictMode, skipping a talk. Always schedule via setTimeout (clamped to 0) with a cleanup so the remount cleanup cancels the first invocation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 53eb7c5 commit fde32b0

6 files changed

Lines changed: 54 additions & 32 deletions

File tree

functions/src/api/routes/whatsapp/whatsapp.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ export const whatsappRoutes = (fastify: FastifyInstance, options: any, done: ()
290290
)
291291
)
292292
} catch (err) {
293-
request.log?.error({ err }, 'failed to schedule whatsapp panel reminders')
293+
// console.* (not request.log) so this reaches Cloud Logging in production, where the
294+
// Fastify logger is disabled.
295+
console.error('[whatsapp go] failed to schedule panel reminders', err)
294296
}
295297

296298
reply.status(200).send({ sent: true })

src/public/transcription/TranscriptionApp.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useLocalStorage } from '@uidotdev/usehooks'
22
import { Box, Button, Container, TextField, Typography } from '@mui/material'
3-
import { useState } from 'react'
3+
import { useCallback, useState } from 'react'
44
import { usePasswordProtectedEvent } from '../hooks/usePasswordProtectedEvent'
55
import { useTalkSelection } from './useTalkSelection'
6-
import { useAutoReloadAfterTalk } from './useAutoReloadAfterTalk'
6+
import { useAutoAdvanceAfterTalk } from './useAutoAdvanceAfterTalk'
77
import { LiveTranscriptionView } from './LiveTranscriptionView'
88
import { DateTime } from 'luxon'
99

@@ -21,8 +21,15 @@ export const TranscriptionApp = ({ eventId }: PublicEventTranscriptionProps) =>
2121

2222
const [selectedTalk, upcomingTalks, resetSelectedTalk, setSelectedTalk] = useTalkSelection(selectedTrack, eventData)
2323

24-
// Roll the screen over automatically 5 min after the current talk ends.
25-
useAutoReloadAfterTalk(selectedTalk?.dateEnd)
24+
const advanceToNextTalk = useCallback(() => {
25+
if (!selectedTalk) return
26+
const currentIndex = upcomingTalks.findIndex((t) => t.id === selectedTalk.id)
27+
const next = upcomingTalks[currentIndex + 1]
28+
if (next) setSelectedTalk(next)
29+
}, [selectedTalk, upcomingTalks, setSelectedTalk])
30+
31+
// Roll over to the next talk automatically 5 min after the current one ends.
32+
useAutoAdvanceAfterTalk(selectedTalk?.dateEnd, advanceToNextTalk)
2633

2734
const saveStuffInLocalStorage = (pagePassword: string, selectedTrack: string) => {
2835
setSelectedTrack(selectedTrack)

src/public/transcription/TranscriptionSettingsPanel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,14 @@ export const TranscriptionSettingsPanel = ({ settings, onChange }: Transcription
126126
label="Endpointing (s)"
127127
inputProps={{ step: 0.05, min: 0 }}
128128
value={settings.endpointing}
129-
onChange={(e) => set('endpointing', Number(e.target.value) || settings.endpointing)}
129+
onChange={(e) => {
130+
const parsed = Number(e.target.value)
131+
// Keep 0 (valid = disable endpointing); only fall back on empty/invalid input.
132+
set(
133+
'endpointing',
134+
e.target.value === '' || Number.isNaN(parsed) || parsed < 0 ? settings.endpointing : parsed
135+
)
136+
}}
130137
sx={fieldSx}
131138
/>
132139
<Button variant="contained" size="small" onClick={() => set('hideSettings', true)} sx={{ ml: 'auto' }}>

src/public/transcription/transcriptionSettings.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ export const toHexColor = (value: string): string => (value.startsWith('#') ? va
4444
// Read settings from a URL query string, falling back to defaults for anything missing/invalid.
4545
export const settingsFromQuery = (search: string): TranscriptionSettings => {
4646
const q = new URLSearchParams(search)
47-
const num = (key: string, fallback: number) => {
48-
const parsed = Number(q.get(key))
49-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback
47+
const num = (key: string, fallback: number, allowZero = false) => {
48+
const raw = q.get(key)
49+
if (raw === null) return fallback
50+
const parsed = Number(raw)
51+
if (!Number.isFinite(parsed)) return fallback
52+
return parsed > 0 || (allowZero && parsed === 0) ? parsed : fallback
5053
}
5154
const csv = (raw: string | null): string[] | null =>
5255
raw
@@ -69,7 +72,7 @@ export const settingsFromQuery = (search: string): TranscriptionSettings => {
6972
alignment: alignment === 'left' || alignment === 'right' ? alignment : DEFAULT_SETTINGS.alignment,
7073
languages: languages?.length ? languages : DEFAULT_SETTINGS.languages,
7174
customVocabulary: csv(q.get('custom_vocabulary')) ?? DEFAULT_SETTINGS.customVocabulary,
72-
endpointing: num('endpointing', DEFAULT_SETTINGS.endpointing),
75+
endpointing: num('endpointing', DEFAULT_SETTINGS.endpointing, true),
7376
hideSettings: q.has('hide_settings') ? q.get('hide_settings') === 'true' : DEFAULT_SETTINGS.hideSettings,
7477
}
7578
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useEffect } from 'react'
2+
import { DateTime } from 'luxon'
3+
4+
const ADVANCE_DELAY_MS = 5 * 60 * 1000
5+
// setTimeout delays above 2^31-1 ms overflow and fire immediately — ignore anything that far out.
6+
const MAX_TIMEOUT_MS = 2 ** 31 - 1
7+
8+
// Advances to the next talk 5 minutes after the current one ends, so the caption screen rolls over
9+
// without anyone touching the machine.
10+
export const useAutoAdvanceAfterTalk = (talkEndIso: string | undefined, onAdvance: () => void) => {
11+
useEffect(() => {
12+
if (!talkEndIso) return
13+
const end = DateTime.fromISO(talkEndIso)
14+
if (!end.isValid) return
15+
16+
const delay = end.toMillis() + ADVANCE_DELAY_MS - DateTime.now().toMillis()
17+
if (delay > MAX_TIMEOUT_MS) return
18+
19+
// Always go through setTimeout (clamped to 0 for already-ended talks) rather than calling
20+
// onAdvance synchronously: the returned cleanup lets React 18 StrictMode's dev remount cancel
21+
// the first run, so we don't advance twice / skip a talk.
22+
const timer = setTimeout(onAdvance, Math.max(0, delay))
23+
return () => clearTimeout(timer)
24+
}, [talkEndIso, onAdvance])
25+
}

src/public/transcription/useAutoReloadAfterTalk.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)