mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
tour: separate spoken narration from bubble text
Welcome step's "Use Next or your arrow keys" hint stays in the bubble but is dropped from the voice clip (its tail kept synthesizing with gibberish). Steps gain an optional voiceText override, honored by both the clip generator and the live-TTS fallback; the generator also takes step ids as args to re-roll individual clips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b028e56e44
commit
4b8334f457
3 changed files with 21 additions and 5 deletions
|
|
@ -10,6 +10,10 @@
|
|||
* Run whenever a step's narration text or the tour voice changes:
|
||||
* cd apps/x && npm run deps # script imports core's built output
|
||||
* node apps/renderer/scripts/generate-tour-audio.mjs
|
||||
*
|
||||
* Pass step ids to regenerate only those clips (e.g. to re-roll one whose
|
||||
* synthesis came out glitchy):
|
||||
* node apps/renderer/scripts/generate-tour-audio.mjs welcome done
|
||||
*/
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
|
@ -29,15 +33,24 @@ if (start === -1 || end === -1) throw new Error('Could not locate TOUR_STEPS in
|
|||
const block = src.slice(start, end)
|
||||
|
||||
const steps = []
|
||||
const re = /id: '([^']+)'[\s\S]*?text:\s*("[^"]*"|'[^']*')/g
|
||||
// voiceText, when present, is the spoken variant of the bubble text.
|
||||
const re = /id: '([^']+)'[\s\S]*?text:\s*("[^"]*"|'[^']*')(?:,\s*voiceText:\s*("[^"]*"|'[^']*'))?/g
|
||||
for (let m; (m = re.exec(block)); ) {
|
||||
// The capture is a JS string literal from our own source; evaluate it to
|
||||
// resolve the quoting.
|
||||
steps.push({ id: m[1], text: new Function(`return ${m[2]}`)() })
|
||||
// The captures are JS string literals from our own source; evaluate them
|
||||
// to resolve the quoting.
|
||||
steps.push({ id: m[1], text: new Function(`return ${m[3] ?? m[2]}`)() })
|
||||
}
|
||||
if (steps.length === 0) throw new Error('Parsed zero tour steps — regex out of sync with product-tour.tsx?')
|
||||
console.log(`Parsed ${steps.length} tour steps`)
|
||||
|
||||
const only = process.argv.slice(2)
|
||||
if (only.length > 0) {
|
||||
const unknown = only.filter((id) => !steps.some((s) => s.id === id))
|
||||
if (unknown.length > 0) throw new Error(`Unknown step ids: ${unknown.join(', ')}`)
|
||||
steps.splice(0, steps.length, ...steps.filter((s) => only.includes(s.id)))
|
||||
console.log(`Regenerating only: ${only.join(', ')}`)
|
||||
}
|
||||
|
||||
await mkdir(outDir, { recursive: true })
|
||||
for (const step of steps) {
|
||||
process.stdout.write(`synthesizing ${step.id}... `)
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -40,6 +40,8 @@ type TourStep = {
|
|||
vignette?: TourVignetteKind
|
||||
title: string
|
||||
text: string
|
||||
/** Spoken narration, when it should differ from the bubble text. */
|
||||
voiceText?: string
|
||||
}
|
||||
|
||||
const TOUR_STEPS: TourStep[] = [
|
||||
|
|
@ -47,6 +49,7 @@ const TOUR_STEPS: TourStep[] = [
|
|||
id: 'welcome',
|
||||
title: 'All aboard! ⚓',
|
||||
text: "I'm your captain for the next minute. The lights are down and the water's in — let me row you across Rowboat, one stop at a time. Use Next or your arrow keys.",
|
||||
voiceText: "I'm your captain for the next minute. The lights are down and the water's in — let me row you across Rowboat, one stop at a time.",
|
||||
},
|
||||
{
|
||||
id: 'home',
|
||||
|
|
@ -518,7 +521,7 @@ export function ProductTour({
|
|||
if (clip) {
|
||||
speakUrlRef.current(clip)
|
||||
} else if (ttsAvailableRef.current) {
|
||||
speakRef.current(step.text)
|
||||
speakRef.current(step.voiceText ?? step.text)
|
||||
}
|
||||
if (stepIndex === TOUR_STEPS.length - 1) {
|
||||
setConfettiOn(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue