Files
2026-06-07 03:58:32 -04:00

94 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 },
];
}