"use client"; import { useState, useEffect } from "react"; import { TEMPLATE_TYPES, PLATFORM_PRESET_KEYS } from "@pipeline/shared"; const TEMPLATES_INFO: Record = { news: { label: "资讯报道", desc: "Breaking news style with headline cards and ticker", color: "#1a56db" }, knowledge: { label: "知识讲解", desc: "Step-by-step explanation with key point cards", color: "#0d9488" }, opinion: { label: "观点分享", desc: "Quote-style layout for editorial content", color: "#ea580c" }, marketing: { label: "产品营销", desc: "Product showcase with CTA overlays", color: "#9333ea" }, }; const PLATFORMS_INFO: Record = { bilibili: "Bilibili 1920x1080 16:9", "douyin-long": "抖音 竖屏长视频 1080x1920", "douyin-short": "抖音 竖屏短视频 1080x1920", "universal-16x9": "通用横屏 1920x1080", "universal-9x16": "通用竖屏 1080x1920", }; const JSON_PLACEHOLDER = `{ "title": "标题", "subtitle": "副标题", "cover": { "keyframes": [ { "type": "text", "content": "要点预览" } ] }, "scenes": [ { "id": "scene-1", "narration": "配音文本", "keyframes": [ { "type": "text", "content": "要点一" } ] } ], "outro": { "text": "感谢观看", "cta": "点赞 | 关注" } }`; type Step = "input" | "style" | "voice" | "review"; type InputMode = "manual" | "source"; interface SourceInfo { name: string; url: string; config: Record; } export default function CreatePage() { const [step, setStep] = useState("input"); const [inputMode, setInputMode] = useState("manual"); const [text, setText] = useState(""); const [template, setTemplate] = useState("news"); const [platforms, setPlatforms] = useState(["bilibili"]); const [ttsProvider, setTtsProvider] = useState("openai-tts"); const [voiceId, setVoiceId] = useState(""); const [noTts, setNoTts] = useState(false); const [submitting, setSubmitting] = useState(false); const [jobId, setJobId] = useState(null); const [error, setError] = useState(null); // Data source state const [sources, setSources] = useState([]); const [selectedSource, setSelectedSource] = useState(""); const [sourceArgs, setSourceArgs] = useState>({}); const [collecting, setCollecting] = useState(false); const [collectError, setCollectError] = useState(null); useEffect(() => { fetch("/api/collect/sources") .then((res) => res.json()) .then((data) => { setSources(data.sources ?? []); if (data.sources?.length > 0 && !selectedSource) { setSelectedSource(data.sources[0].name); } }) .catch(() => {}); }, []); const steps: { key: Step; label: string }[] = [ { key: "input", label: "1. Input" }, { key: "style", label: "2. Template" }, { key: "voice", label: "3. Voice" }, { key: "review", label: "4. Review" }, ]; const stepIndex = steps.findIndex((s) => s.key === step); async function handleCollect() { if (!selectedSource) return; setCollecting(true); setCollectError(null); try { const res = await fetch("/api/collect", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ source: selectedSource, args: sourceArgs }), }); const data = await res.json(); if (data.error) { setCollectError(data.error); } else { setText(data.content); } } catch (err: any) { setCollectError(err.message); } finally { setCollecting(false); } } async function handleRender() { setSubmitting(true); setError(null); try { const payload: any = { input: { template, platforms, ttsProvider, voiceId }, options: { noTts }, }; // Prefer already-collected (and possibly edited) text. Only fall back to // server-side collection if the textarea is still empty. if (text.trim()) { payload.input.text = text; } else if (inputMode === "source" && selectedSource) { payload.input.source = selectedSource; if (Object.keys(sourceArgs).length > 0) { payload.input.sourceArgs = sourceArgs; } } const res = await fetch("/api/render", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await res.json(); if (data.jobId) { setJobId(data.jobId); } else { setError(data.error || "Unknown error"); } } catch (err: any) { setError(err.message); } finally { setSubmitting(false); } } const canProceed = inputMode === "manual" ? text.trim().length > 0 : selectedSource.length > 0; const togglePlatform = (p: string) => { setPlatforms((prev) => prev.includes(p) ? prev.filter((x) => x !== p) : [...prev, p] ); }; // Source-specific arg fields const currentSource = sources.find((s) => s.name === selectedSource); const needsOwnerRepo = currentSource?.name === "github-repo"; if (jobId) { return (

Rendering...

Your video is being generated

Job ID: {jobId}

); } return (

Create Video

Provide content manually or collect from external data sources

{steps.map((s, i) => (
{s.label}
))}
{step === "input" && (
{/* Mode toggle */}
{inputMode === "manual" && (