"use client"; import { useState } from "react"; type PipelineResult = { videoPath: string; finalAudioPath: string; imageFiles: string[]; }; type ApiSuccess = { ok: true; result: PipelineResult }; type ApiError = { ok: false; error?: string }; type ApiResponse = ApiSuccess | ApiError; function isApiResponse(value: unknown): value is ApiResponse { if (typeof value !== "object" || value === null) return false; const v = value as Record; if (v.ok === true) { const r = v.result as Record | undefined; return ( typeof r === "object" && r !== null && typeof r.videoPath === "string" && typeof r.finalAudioPath === "string" && Array.isArray(r.imageFiles) ); } return v.ok === false; } export default function Home() { const [storyName, setStoryName] = useState("sample_story"); const [concurrency, setConcurrency] = useState(3); const [force, setForce] = useState(false); const [skipUpload, setSkipUpload] = useState(true); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); async function runPipeline(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(null); setResult(null); try { const res = await fetch("/api/run", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ storyName, concurrency, force, skipUpload }), }); const data: unknown = await res.json(); if (!res.ok || !isApiResponse(data) || data.ok !== true) { const msg = isApiResponse(data) && data.ok === false ? data.error : undefined; throw new Error(msg || `Request failed with status ${res.status}`); } setResult(data.result); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); setError(message); } finally { setLoading(false); } } return (

Audiobooks Hustle – Run Pipeline

setStoryName(e.target.value)} required />

Must match a folder in the repo root at stories/<storyName> containing source.txt and config.yaml.

setConcurrency(parseInt(e.target.value || "3", 10))} />
{error &&
{error}
} {result && (
Video created:
{result.videoPath}
Final audio:
{result.finalAudioPath}
Images ({result.imageFiles?.length ?? 0}):
{(result.imageFiles || []).join("\n")}
)}
); }