94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
|
|
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 3–6 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 },
|
|||
|
|
];
|
|||
|
|
}
|