Download src/components/study-chat/DeepChapterCards.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 18.3 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/DeepChapterCards.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/study-chat/DeepChapterCards.tsx
-
curl -L -o DeepChapterCards.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/DeepChapterCards.tsx
18.3 kB
| "use client"; | |
| import { | |
| BookOpen, | |
| Calculator, | |
| ChevronDown, | |
| ChevronUp, | |
| Lightbulb, | |
| Target, | |
| Zap, | |
| } from "lucide-react"; | |
| import { useState } from "react"; | |
| import type { DeepChapterOutput, VisualLessonOutput } from "@/lib/api/types"; | |
| export function DeepChapterCard({ data, topic }: { data: DeepChapterOutput; topic: string }) { | |
| const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set(["concepts"])); | |
| const toggle = (key: string) => { | |
| setExpandedSections((prev) => { | |
| const next = new Set(prev); | |
| if (next.has(key)) next.delete(key); | |
| else next.add(key); | |
| return next; | |
| }); | |
| }; | |
| const dataLabel = data.data_source_label; | |
| const pyqPatterns = data.pyq_patterns ?? []; | |
| const hasRealPyq = pyqPatterns.some((p) => p.question && p.source === "Seen in uploaded PYQs"); | |
| return ( | |
| <article className="animate-msg-assistant w-full rounded-xl border border-white/[0.06] bg-[#080809] p-5"> | |
| {/* Header */} | |
| <div className="mb-4 flex items-center gap-2.5"> | |
| <BookOpen className="size-4 shrink-0 text-blue-400/70" /> | |
| <div> | |
| <span className="text-[10px] font-medium uppercase tracking-[0.08em] text-white/22">Deep Chapter Teaching</span> | |
| <p className="text-[15px] font-semibold tracking-[-0.01em] text-white/95">{data.chapter_title || topic}</p> | |
| </div> | |
| </div> | |
| {/* Data source label */} | |
| {dataLabel && ( | |
| <div className="mb-2 inline-flex items-center gap-1.5 rounded-md border border-white/[0.06] bg-white/[0.03] px-2 py-1"> | |
| <span className="size-1.5 rounded-full bg-emerald-400/60" /> | |
| <span className="text-[10px] text-white/40">{dataLabel}</span> | |
| </div> | |
| )} | |
| {/* Syllabus position */} | |
| <p className="mb-3 text-[11px] text-white/35">{data.syllabus_position}</p> | |
| {/* What this chapter is about */} | |
| {data.what_this_chapter_is_about && ( | |
| <div className="mb-3 rounded-lg border border-white/[0.06] bg-white/[0.02] px-3 py-2.5"> | |
| <p className="text-[13px] leading-[1.7] text-white/75">{data.what_this_chapter_is_about}</p> | |
| </div> | |
| )} | |
| {/* Prerequisites */} | |
| {data.prerequisite_topics?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Prerequisites" | |
| icon={<Target className="size-3" />} | |
| expanded={expandedSections.has("prereqs")} | |
| onToggle={() => toggle("prereqs")} | |
| count={data.prerequisite_topics.length} | |
| > | |
| <ul className="space-y-1"> | |
| {data.prerequisite_topics.map((p, i) => ( | |
| <li key={i} className="flex items-start gap-2 text-[12px] text-white/60"> | |
| <span className="mt-1 size-1 shrink-0 rounded-full bg-blue-400/50" /> | |
| {p} | |
| </li> | |
| ))} | |
| </ul> | |
| </CollapsibleSection> | |
| )} | |
| {/* Deep concept sections */} | |
| {data.deep_concept_sections?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Deep Teaching" | |
| icon={<Lightbulb className="size-3" />} | |
| expanded={expandedSections.has("concepts")} | |
| onToggle={() => toggle("concepts")} | |
| count={data.deep_concept_sections.length} | |
| > | |
| <div className="space-y-3"> | |
| {data.deep_concept_sections.map((concept, i) => ( | |
| <div key={i} className="rounded-lg border border-white/[0.04] bg-white/[0.01] p-3"> | |
| <p className="text-[13px] font-semibold text-white/85">{concept.name}</p> | |
| <p className="mt-1 text-[12px] leading-[1.6] text-white/60">{concept.simple_explanation}</p> | |
| {concept.deep_explanation && ( | |
| <p className="mt-1 text-[12px] leading-[1.6] text-white/50">{concept.deep_explanation}</p> | |
| )} | |
| {concept.equation && ( | |
| <p className="mt-1 rounded bg-black/30 px-2 py-1 text-[12px] font-mono text-emerald-400/80">{concept.equation}</p> | |
| )} | |
| {concept.real_example && ( | |
| <p className="mt-1 text-[11px] text-white/40">Example: {concept.real_example}</p> | |
| )} | |
| {concept.common_mistake && ( | |
| <p className="mt-1 text-[11px] text-amber-400/60">Mistake: {concept.common_mistake}</p> | |
| )} | |
| {concept.practice_question && ( | |
| <p className="mt-1 text-[11px] text-white/55">Try: {concept.practice_question}</p> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Derivations */} | |
| {data.derivations?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Derivations" | |
| icon={<Calculator className="size-3" />} | |
| expanded={expandedSections.has("derivations")} | |
| onToggle={() => toggle("derivations")} | |
| count={data.derivations.length} | |
| > | |
| <div className="space-y-3"> | |
| {data.derivations.map((d, i) => ( | |
| <div key={i} className="rounded-lg border border-emerald-500/10 bg-emerald-500/[0.03] p-3"> | |
| <p className="text-[13px] font-semibold text-white/85">{d.name}</p> | |
| {d.symbols?.length > 0 && ( | |
| <p className="mt-1 text-[11px] text-white/40">Symbols: {d.symbols.join(", ")}</p> | |
| )} | |
| {d.steps?.length > 0 && ( | |
| <ol className="mt-2 space-y-1"> | |
| {d.steps.map((step, j) => ( | |
| <li key={j} className="flex items-start gap-2 text-[12px] text-white/60"> | |
| <span className="mt-0.5 text-[10px] text-white/30">{j + 1}.</span> | |
| {step} | |
| </li> | |
| ))} | |
| </ol> | |
| )} | |
| {d.final_formula && ( | |
| <p className="mt-2 rounded bg-black/30 px-2 py-1 text-[12px] font-mono text-emerald-400/80">{d.final_formula}</p> | |
| )} | |
| {d.unit_check && <p className="mt-1 text-[11px] text-white/40">{d.unit_check}</p>} | |
| </div> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Formulas */} | |
| {data.formulas?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Formulas" | |
| icon={<Zap className="size-3" />} | |
| expanded={expandedSections.has("formulas")} | |
| onToggle={() => toggle("formulas")} | |
| count={data.formulas.length} | |
| > | |
| <div className="space-y-1.5"> | |
| {data.formulas.map((f, i) => ( | |
| <div key={i} className="flex items-start gap-2 rounded-lg border border-white/[0.04] bg-white/[0.01] px-3 py-2"> | |
| <span className="min-w-0 flex-1 text-[12px] text-white/70">{f.name}</span> | |
| <span className="shrink-0 font-mono text-[11px] text-emerald-400/70">{f.formula}</span> | |
| <span className="shrink-0 text-[10px] text-white/30">[{f.unit}]</span> | |
| </div> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Common mistakes */} | |
| {data.common_mistakes?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Common Mistakes" | |
| expanded={expandedSections.has("mistakes")} | |
| onToggle={() => toggle("mistakes")} | |
| count={data.common_mistakes.length} | |
| > | |
| <ul className="space-y-1"> | |
| {data.common_mistakes.map((m, i) => ( | |
| <li key={i} className="flex items-start gap-2 text-[12px] text-amber-400/60"> | |
| <span className="mt-1 size-1 shrink-0 rounded-full bg-amber-400/50" /> | |
| {m} | |
| </li> | |
| ))} | |
| </ul> | |
| </CollapsibleSection> | |
| )} | |
| {/* Important questions */} | |
| {data.important_questions?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Important Questions" | |
| expanded={expandedSections.has("important")} | |
| onToggle={() => toggle("important")} | |
| count={data.important_questions.length} | |
| > | |
| <ul className="space-y-1"> | |
| {data.important_questions.map((q, i) => ( | |
| <li key={i} className="flex items-start gap-2 text-[12px] text-white/60"> | |
| <span className="mt-1 size-1 shrink-0 rounded-full bg-white/45" /> | |
| {q} | |
| </li> | |
| ))} | |
| </ul> | |
| </CollapsibleSection> | |
| )} | |
| {/* PYQ */} | |
| {hasRealPyq ? ( | |
| <CollapsibleSection | |
| title="PYQ Patterns (Uploaded Papers)" | |
| expanded={expandedSections.has("pyq")} | |
| onToggle={() => toggle("pyq")} | |
| count={pyqPatterns.filter((p) => p.question).length} | |
| > | |
| <div className="space-y-1.5"> | |
| {pyqPatterns.filter((p) => p.question).map((p, i) => ( | |
| <div key={i} className="rounded-lg border border-white/[0.04] bg-white/[0.01] px-3 py-2"> | |
| <p className="text-[12px] text-white/70">{p.question}</p> | |
| <div className="mt-0.5 flex items-center gap-2"> | |
| <span className="text-[10px] text-white/35">{p.marks} marks — {p.year}</span> | |
| {p.source === "Seen in uploaded PYQs" && ( | |
| <span className="rounded bg-emerald-500/10 px-1.5 py-0.5 text-[9px] text-emerald-400/70">Uploaded PYQ</span> | |
| )} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| ) : ( | |
| <div className="mt-3 rounded-lg border border-amber-500/10 bg-amber-500/[0.03] px-3 py-2.5"> | |
| <p className="text-[11px] font-medium text-amber-400/70">Upload previous year papers to unlock pattern analysis across them.</p> | |
| <p className="mt-1 text-[10px] text-amber-400/50">Upload previous year question papers to see repeated topics, marks distribution, and evidence-backed insights.</p> | |
| </div> | |
| )} | |
| {/* Quick revision */} | |
| {data.quick_revision_box?.length > 0 && ( | |
| <div className="mt-3 rounded-lg border border-blue-500/10 bg-blue-500/[0.03] px-3 py-2.5"> | |
| <p className="mb-1.5 text-[10px] font-medium uppercase tracking-[0.08em] text-blue-400/50">Quick Revision</p> | |
| <ul className="space-y-1"> | |
| {data.quick_revision_box.map((r, i) => ( | |
| <li key={i} className="text-[12px] text-white/60">{r}</li> | |
| ))} | |
| </ul> | |
| </div> | |
| )} | |
| {/* Practice */} | |
| {data.practice_questions?.length > 0 && ( | |
| <div className="mt-3 rounded-lg border border-white/[0.1] bg-white/[0.03] px-3 py-2.5"> | |
| <p className="mb-1.5 text-[10px] font-medium uppercase tracking-[0.08em] text-white/50">Practice</p> | |
| <ul className="space-y-1"> | |
| {data.practice_questions.map((q, i) => ( | |
| <li key={i} className="text-[12px] text-white/60">{q}</li> | |
| ))} | |
| </ul> | |
| </div> | |
| )} | |
| {/* Next action */} | |
| {data.next_best_action && ( | |
| <div className="mt-3 rounded-lg border border-emerald-500/10 bg-emerald-500/[0.03] px-3 py-2.5"> | |
| <p className="text-[12px] text-emerald-400/70">{data.next_best_action}</p> | |
| </div> | |
| )} | |
| {/* Source */} | |
| {data.source_used && ( | |
| <p className="mt-3 text-[10px] text-white/25">Source: {data.source_used}</p> | |
| )} | |
| </article> | |
| ); | |
| } | |
| export function VisualLessonCard({ data, topic }: { data: VisualLessonOutput; topic: string }) { | |
| const [expandedFrames, setExpandedFrames] = useState<Set<string>>(new Set(["derivation"])); | |
| const toggle = (key: string) => { | |
| setExpandedFrames((prev) => { | |
| const next = new Set(prev); | |
| if (next.has(key)) next.delete(key); | |
| else next.add(key); | |
| return next; | |
| }); | |
| }; | |
| const dataLabel = data.data_source_label; | |
| return ( | |
| <article className="animate-msg-assistant w-full rounded-xl border border-white/[0.06] bg-[#080809] p-5"> | |
| <div className="mb-4 flex items-center gap-2.5"> | |
| <BookOpen className="size-4 shrink-0 text-white/70" /> | |
| <div> | |
| <span className="text-[10px] font-medium uppercase tracking-[0.08em] text-white/22">Visual Lesson Plan</span> | |
| <p className="text-[15px] font-semibold tracking-[-0.01em] text-white/95">{data.lesson_title || topic}</p> | |
| </div> | |
| </div> | |
| {/* Data source label */} | |
| {dataLabel && ( | |
| <div className="mb-2 inline-flex items-center gap-1.5 rounded-md border border-white/[0.06] bg-white/[0.03] px-2 py-1"> | |
| <span className="size-1.5 rounded-full bg-emerald-400/60" /> | |
| <span className="text-[10px] text-white/40">{dataLabel}</span> | |
| </div> | |
| )} | |
| <p className="mb-2 text-[11px] text-white/35">{data.syllabus_position}</p> | |
| <p className="mb-3 text-[12px] text-white/50">{data.learning_goal}</p> | |
| {/* Stats */} | |
| <div className="mb-4 flex gap-3"> | |
| <span className="rounded bg-white/[0.04] px-2 py-1 text-[11px] text-white/50">{data.total_frames} frames</span> | |
| <span className="rounded bg-white/[0.04] px-2 py-1 text-[11px] text-white/50">{Math.round(data.total_estimated_seconds / 60)} min</span> | |
| <span className="rounded bg-white/[0.04] px-2 py-1 text-[11px] text-white/50">{data.visual_style}</span> | |
| </div> | |
| {/* Concept flow */} | |
| {data.concept_flow?.length > 0 && ( | |
| <div className="mb-4"> | |
| <p className="mb-1.5 text-[10px] font-medium uppercase tracking-[0.08em] text-white/22">Concept Flow</p> | |
| <div className="flex flex-wrap gap-1.5"> | |
| {data.concept_flow.map((step, i) => ( | |
| <span key={i} className="rounded-md border border-white/[0.06] bg-white/[0.02] px-2 py-1 text-[11px] text-white/50"> | |
| {i + 1}. {step} | |
| </span> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {/* Derivation frames */} | |
| {data.derivation_frames?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Derivation Frames" | |
| icon={<Calculator className="size-3" />} | |
| expanded={expandedFrames.has("derivation")} | |
| onToggle={() => toggle("derivation")} | |
| count={data.derivation_frames.length} | |
| > | |
| <div className="space-y-3"> | |
| {data.derivation_frames.map((frame) => ( | |
| <FrameCard key={frame.frame_number} frame={frame} /> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Diagram frames */} | |
| {data.diagram_frames?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Diagram Frames" | |
| expanded={expandedFrames.has("diagram")} | |
| onToggle={() => toggle("diagram")} | |
| count={data.diagram_frames.length} | |
| > | |
| <div className="space-y-3"> | |
| {data.diagram_frames.map((frame) => ( | |
| <FrameCard key={frame.frame_number} frame={frame} /> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Numerical frames */} | |
| {data.numerical_frames?.length > 0 && ( | |
| <CollapsibleSection | |
| title="Numerical Frames" | |
| icon={<Calculator className="size-3" />} | |
| expanded={expandedFrames.has("numerical")} | |
| onToggle={() => toggle("numerical")} | |
| count={data.numerical_frames.length} | |
| > | |
| <div className="space-y-3"> | |
| {data.numerical_frames.map((frame) => ( | |
| <FrameCard key={frame.frame_number} frame={frame} /> | |
| ))} | |
| </div> | |
| </CollapsibleSection> | |
| )} | |
| {/* Narration script */} | |
| {data.narration_script && ( | |
| <div className="mt-3 rounded-lg border border-white/[0.04] bg-white/[0.01] px-3 py-2.5"> | |
| <p className="mb-1 text-[10px] font-medium uppercase tracking-[0.08em] text-white/20">Narration Script</p> | |
| <p className="text-[12px] leading-[1.6] text-white/50 whitespace-pre-wrap">{data.narration_script.substring(0, 500)}{data.narration_script.length > 500 ? "..." : ""}</p> | |
| </div> | |
| )} | |
| </article> | |
| ); | |
| } | |
| function FrameCard({ frame }: { frame: { frame_number: number; teaching_purpose: string; visual_type: string; on_screen_text: string; narration: string; why_this_helps: string; exam_connection: string; duration_seconds: number; common_misunderstanding_fixed?: string } }) { | |
| return ( | |
| <div className="rounded-lg border border-white/[0.04] bg-white/[0.01] p-3"> | |
| <div className="flex items-center justify-between gap-2"> | |
| <span className="text-[11px] font-medium text-white/50">Frame {frame.frame_number}</span> | |
| <span className="rounded bg-white/[0.04] px-1.5 py-0.5 text-[10px] text-white/35">{frame.visual_type}</span> | |
| <span className="text-[10px] text-white/25">{frame.duration_seconds}s</span> | |
| </div> | |
| <p className="mt-1 text-[12px] font-medium text-white/70">{frame.teaching_purpose}</p> | |
| <p className="mt-1 rounded bg-black/30 px-2 py-1 text-[11px] font-mono text-emerald-400/70 whitespace-pre-wrap">{frame.on_screen_text}</p> | |
| <p className="mt-1 text-[11px] text-white/45">{frame.narration}</p> | |
| {frame.common_misunderstanding_fixed && ( | |
| <p className="mt-1 text-[10px] text-amber-400/50">Fixes: {frame.common_misunderstanding_fixed}</p> | |
| )} | |
| <p className="mt-1 text-[10px] text-white/50">Exam: {frame.exam_connection}</p> | |
| </div> | |
| ); | |
| } | |
| function CollapsibleSection({ | |
| title, | |
| icon, | |
| expanded, | |
| onToggle, | |
| count, | |
| children, | |
| }: { | |
| title: string; | |
| icon?: React.ReactNode; | |
| expanded: boolean; | |
| onToggle: () => void; | |
| count: number; | |
| children: React.ReactNode; | |
| }) { | |
| return ( | |
| <div className="mt-3"> | |
| <button | |
| type="button" | |
| onClick={onToggle} | |
| className="flex w-full items-center justify-between gap-2 rounded-lg border border-white/[0.06] bg-white/[0.02] px-3 py-2 transition hover:bg-white/[0.04]" | |
| > | |
| <span className="flex items-center gap-1.5 text-[11px] font-medium text-white/50"> | |
| {icon} | |
| {title} | |
| <span className="text-[10px] text-white/25">({count})</span> | |
| </span> | |
| {expanded ? <ChevronUp className="size-3 text-white/30" /> : <ChevronDown className="size-3 text-white/30" />} | |
| </button> | |
| {expanded && <div className="mt-2 pl-1">{children}</div>} | |
| </div> | |
| ); | |
| } | |