The third segment of the note-powered wizards (Study deck / Homework / Podcast) takes the same intake - photos, documents, pasted text, the share target - and turns it into a spoken episode. - prompts: PODCAST_SYSTEM_PROMPT writes for the ear (plain prose, no markdown, no visual references), chaptered into 3-8 sections around a length target. - /api/podcast mirrors /api/generate's plumbing (rate limit, model gate, provider keys, heartbeats, the 2x deadline, the optional vision pass). - storage: frezmo.podcasts.v1, zod-validated, included in the backup and the encrypted sync payload; a new library tab lists episodes with listen / rename / delete-with-undo, and the wizard switch deep-links in. - player: the transcript is word-wrapped spans and the word under the voice rides a lyric-style highlight that keeps itself centred. Two clocks: exact onboundary events where the browser reports them, a per-word length timer where it does not (iOS Safari); the first boundary retires the timer. Pause = cancel + re-speak from the word, which makes resume, chapter jumps and tap-any-word-to-seek the same operation; the position saves on pause, page-hide and unmount, so reopening resumes. Speed rebuilds the engine and keeps playing; the Aero soundtrack pauses itself when the voice starts; a browser with no voice gets an honest note and a readable transcript. - the script sits on a glass pane (plain paragraphs over Aero's grass failed contrast; the scan now covers the player too). tests/podcast-test.mjs (17th suite) stubs the model and the speech engine and drives the highlight by hand: 26 checks. All suites green, contrast clean. Version 0.7.3, SW v95.
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
import { ApiKeyGate } from "@/components/ApiKeyGate";
|
|
import { AppHeader } from "@/components/AppHeader";
|
|
import { RouteTransition } from "@/components/RouteTransition";
|
|
import { Sidebar, SidebarOffset } from "@/components/Sidebar";
|
|
import { PodcastWizard } from "@/components/podcast/PodcastWizard";
|
|
import { WizardModeSwitch } from "@/components/WizardModeSwitch";
|
|
|
|
export const metadata = {
|
|
title: "New podcast",
|
|
};
|
|
|
|
/**
|
|
* Podcast mode's intake: the same notes as a deck, but the output is a spoken
|
|
* episode with a synced transcript (see components/podcast).
|
|
*/
|
|
export default function PodcastPage() {
|
|
return (
|
|
<ApiKeyGate>
|
|
<div className="flex min-h-dvh">
|
|
<Sidebar />
|
|
<SidebarOffset />
|
|
<main className="flex min-h-dvh min-w-0 flex-1 flex-col">
|
|
<AppHeader />
|
|
<div className="mx-auto w-full max-w-3xl flex-1 px-4 pb-tabbar lg:px-10 lg:pb-16 lg:pt-10">
|
|
<WizardModeSwitch mode="podcast" />
|
|
<RouteTransition>
|
|
<PodcastWizard />
|
|
</RouteTransition>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</ApiKeyGate>
|
|
);
|
|
}
|