Initial commit: PodcastYes — AI podcast platform

This commit is contained in:
Leon Serfaty
2026-06-07 03:58:32 -04:00
commit 155507f21a
151 changed files with 19826 additions and 0 deletions
+93
View File
@@ -0,0 +1,93 @@
import type { EpisodeConfig, StructuredScript } from "../types";
const FORMAT_GUIDANCE: Record<EpisodeConfig["format"], string> = {
SOLO: "A single host speaking directly to the listener. Use only the host speaker.",
INTERVIEW:
"A host interviewing a guest. Alternate naturally between host questions and guest answers.",
MULTI_HOST:
"A panel of co-hosts in lively conversation. Distribute lines across all speakers and let them react to each other.",
};
/** Roughly 150 spoken words per minute → target word budget for the whole episode. */
function wordBudget(minutes: number): number {
return Math.round(minutes * 150);
}
export function buildScriptMessages(config: EpisodeConfig) {
const speakerList = config.speakers
.map((s) => `- key "${s.speakerKey}" = ${s.displayName}`)
.join("\n");
const system = [
"You are an expert podcast scriptwriter and showrunner.",
"You write natural, engaging, spoken-word scripts that sound great when read aloud by AI voices.",
"Avoid stage directions, sound-effect notes, and parentheticals — output only spoken dialogue.",
"Return STRICT JSON only, matching the requested schema. Do not include markdown fences.",
].join(" ");
const user = [
`Write a complete podcast episode script in ${config.language}.`,
"",
`Topic: ${config.topic}`,
`Tone: ${config.tone}`,
`Format: ${config.format}${FORMAT_GUIDANCE[config.format]}`,
config.audience ? `Target audience: ${config.audience}` : "",
`Approximate length: ${config.targetLengthMin} minutes (~${wordBudget(
config.targetLengthMin
)} words total).`,
"",
"Speakers (use ONLY these keys in `speakerKey`):",
speakerList,
"",
"Structure the episode into 36 sections (e.g. intro, main segments, outro).",
"Each section has a short title and a list of turns. Each turn is one speaker's spoken line.",
"",
"Return JSON with this exact shape:",
`{
"title": "string — a catchy episode title",
"sections": [
{
"id": "kebab-case-id",
"title": "string",
"turns": [
{ "speakerKey": "host", "text": "spoken line..." }
]
}
]
}`,
]
.filter(Boolean)
.join("\n");
return [
{ role: "system" as const, content: system },
{ role: "user" as const, content: user },
];
}
export function buildSectionMessages(
config: EpisodeConfig,
script: StructuredScript,
sectionId: string
) {
const section = script.sections.find((s) => s.id === sectionId);
const speakerList = config.speakers.map((s) => `"${s.speakerKey}"=${s.displayName}`).join(", ");
const system =
"You are an expert podcast scriptwriter. Rewrite a single section of an existing episode, keeping the same speakers, tone, and language. Return STRICT JSON for just that one section.";
const user = [
`Episode title: ${script.title}`,
`Tone: ${config.tone}. Language: ${config.language}. Speakers: ${speakerList}.`,
"",
`Rewrite the section titled "${section?.title ?? sectionId}" (id "${sectionId}") to be fresh and engaging while serving the same purpose in the episode.`,
"",
"Return JSON with this exact shape:",
`{ "id": "${sectionId}", "title": "string", "turns": [ { "speakerKey": "host", "text": "..." } ] }`,
].join("\n");
return [
{ role: "system" as const, content: system },
{ role: "user" as const, content: user },
];
}