DocDoeAI / src /components /video /VideoLessonPreview.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
7.76 kB
import type { VideoLessonPlan } from "@/lib/video/videoLessonPlanner";
type VideoLessonPreviewProps = {
plan: VideoLessonPlan;
};
function Metric({ label, value }: { label: string; value: string | number }) {
return (
<div className="rounded-lg border border-[#2A2A2A] bg-[#050505] p-3">
<p className="text-[10px] font-semibold uppercase tracking-[0.14em] text-[#6B7280]">{label}</p>
<p className="mt-1 text-[16px] font-bold text-white">{value}</p>
</div>
);
}
function SectionLabel({ children }: { children: React.ReactNode }) {
return <p className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[#6B7280]">{children}</p>;
}
function sceneTypeLabel(sceneType: string): string {
const labels: Record<string, string> = {
intro: "class intro",
session_plan: "session plan",
teacher_explanation: "tutor explanation",
board_drawing: "board drawing",
worked_example: "worked example",
method: "DocDoe method",
notes: "notebook notes",
exam_answer: "exam answer",
practice_pause: "practice pause",
quiz: "quiz",
revision: "revision",
outro: "class finish",
};
return labels[sceneType] ?? sceneType.replace(/_/g, " ");
}
function voiceStyleLabel(style: string): string {
const labels: Record<string, string> = {
calm_teacher: "calm tutor",
exam_coach: "exam coach",
quick_revision: "quick revision",
};
return labels[style] ?? style.replace(/_/g, " ");
}
export function VideoLessonPreview({ plan }: VideoLessonPreviewProps) {
const narrationPreview = plan.audioScript.slice(0, 2);
const boardPreview = plan.boardTimeline.slice(0, 3);
const notebookPreview = plan.notebookSections.slice(0, 2);
const practicePreview = plan.practicePauses.slice(0, 2);
return (
<section
aria-label="Video lesson plan preview"
className="rounded-xl border border-[#2A2A2A] bg-[#0B0B0B] p-4 md:p-5"
data-testid="video-lesson-preview"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#6B7280]">Audio/video preview</p>
<h3 className="mt-1 text-xl font-bold tracking-tight text-white">{plan.missionTitle}</h3>
<p className="mt-2 max-w-2xl text-[13px] leading-6 text-[#A1A1AA]">
Video renderer coming later; this is today&apos;s lesson plan. No MP4 or generated video is created here.
</p>
</div>
<span className="rounded-full border border-white/15 bg-white px-3 py-1 text-[11px] font-black uppercase tracking-[0.12em] text-black">
Renderer coming later
</span>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-3">
<Metric label="Duration" value={`${plan.estimatedDurationMinutes} min`} />
<Metric label="Scenes" value={plan.scenes.length} />
<Metric label="Status" value={plan.renderStatus.replace("_", " ")} />
</div>
{plan.warnings.length > 0 ? (
<div className="mt-4 rounded-lg border border-[#3A3A3A] bg-[#111111] p-3">
<SectionLabel>Planner warnings</SectionLabel>
<ul className="mt-2 space-y-1 text-[12px] leading-5 text-[#D4D4D8]">
{plan.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
</div>
) : null}
<div className="mt-5 grid gap-4 xl:grid-cols-[minmax(0,1.15fr)_minmax(0,0.85fr)]">
<div className="space-y-4">
<div data-testid="video-scene-timeline">
<SectionLabel>Lesson timeline</SectionLabel>
<ol className="mt-3 space-y-2">
{plan.scenes.slice(0, 8).map((scene) => (
<li key={scene.id} className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-3">
<div className="flex flex-wrap items-center justify-between gap-2">
<p className="text-[13px] font-semibold text-white">{scene.title}</p>
<span className="font-mono text-[11px] text-[#A1A1AA]">
{scene.startMinute}-{scene.startMinute + scene.durationMinutes} min
</span>
</div>
<p className="mt-1 text-[11px] uppercase tracking-[0.12em] text-[#6B7280]">{sceneTypeLabel(scene.sceneType)}</p>
<p className="mt-2 line-clamp-2 text-[12px] leading-5 text-[#D4D4D8]">{scene.narration[0]}</p>
</li>
))}
</ol>
</div>
<div data-testid="video-board-timeline">
<SectionLabel>Board drawing</SectionLabel>
<div className="mt-3 grid gap-3 md:grid-cols-2">
{boardPreview.map((frame) => (
<article key={frame.id} className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-3">
<p className="text-[13px] font-semibold text-white">{frame.frameTitle}</p>
<p className="mt-1 font-mono text-[11px] text-[#A1A1AA]">at {frame.timestampMinute} min</p>
<ul className="mt-3 space-y-1 text-[12px] leading-5 text-[#D4D4D8]">
{frame.drawingSteps.slice(0, 3).map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</div>
</div>
<div className="space-y-4">
<div className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-3" data-testid="video-narration-preview">
<SectionLabel>Tutor explanation</SectionLabel>
<div className="mt-3 space-y-3">
{narrationPreview.map((block) => (
<div key={block.sceneId} className="rounded-lg border border-white/[0.07] bg-white/[0.035] p-3">
<p className="text-[11px] font-semibold uppercase tracking-[0.12em] text-[#6B7280]">{voiceStyleLabel(block.voiceStyle)}</p>
<p className="mt-2 line-clamp-4 text-[12px] leading-5 text-white">{block.script}</p>
</div>
))}
</div>
</div>
<div className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-3" data-testid="video-notebook-preview">
<SectionLabel>Copy this to notebook</SectionLabel>
<div className="mt-3 space-y-3">
{notebookPreview.map((section) => (
<div key={`${section.sceneId}-${section.title}`}>
<p className="text-[13px] font-semibold text-white">{section.title}</p>
<ul className="mt-2 space-y-1 text-[12px] leading-5 text-[#D4D4D8]">
{section.copyToNotebook.slice(0, 3).map((line) => (
<li key={line}>{line}</li>
))}
</ul>
</div>
))}
</div>
</div>
<div className="rounded-lg border border-[#2A2A2A] bg-[#111111] p-3" data-testid="video-practice-preview">
<SectionLabel>Notice this</SectionLabel>
<div className="mt-3 space-y-3">
{practicePreview.map((pause) => (
<div key={pause.sceneId} className="rounded-lg border border-dashed border-[#3A3A3A] bg-[#0B0B0B] p-3">
<p className="text-[12px] font-semibold leading-5 text-white">{pause.prompt}</p>
<p className="mt-1 text-[11px] text-[#A1A1AA]">Pause: {pause.pauseSeconds} sec</p>
<p className="mt-2 text-[12px] leading-5 text-[#D4D4D8]">Hint: {pause.hint}</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
);
}