DocDoeAI / src /components /curriculum /ChapterIntelHeader.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
6.79 kB
"use client";
// ─── Chapter Intelligence Header ──────────────────────────────────────────────
// Surfaces exam intelligence from curriculum metadata above lesson content.
// Renders nothing if chapter cannot be matched (graceful degradation).
import { Clock, Flame, Star, BookMarked, AlertTriangle } from "lucide-react";
import { searchChapters } from "@/lib/curriculum/index";
import {
getExamProfile,
pyqLabel,
difficultyLabel,
} from "@/lib/curriculum/exam-intelligence";
import { cn } from "@/lib/utils";
// ─── Helpers ──────────────────────────────────────────────────────────────────
function normalizeLevel(raw: string | null | undefined): string | undefined {
if (!raw) return undefined;
const s = raw.toLowerCase();
if (s.includes("sslc") || s.includes("10")) return "SSLC";
if (s.includes("+2") || s.includes("12") || s.includes("plus two") || s.includes("class 12")) return "+2";
if (s.includes("+1") || s.includes("11") || s.includes("plus one") || s.includes("class 11")) return "+1";
return undefined;
}
// ─── Sub-components ───────────────────────────────────────────────────────────
function IntelChip({
children,
variant = "neutral",
}: {
children: React.ReactNode;
variant?: "neutral" | "green" | "amber" | "violet" | "red";
}) {
const variants = {
neutral: "border-white/[0.08] bg-white/[0.04] text-white/50",
green: "border-emerald-400/20 bg-emerald-400/8 text-emerald-400",
amber: "border-amber-400/20 bg-amber-400/8 text-amber-400",
violet: "border-violet-400/20 bg-violet-400/8 text-violet-400",
red: "border-red-400/20 bg-red-400/8 text-red-400",
};
return (
<span
className={cn(
"inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-[11px] font-medium",
variants[variant],
)}
>
{children}
</span>
);
}
// ─── Main component ───────────────────────────────────────────────────────────
type Props = {
/** Chapter name or topic extracted from study request analysis */
chapterQuery: string;
/** Level hint: "+1", "+2", "SSLC", grade string, or exam string */
levelHint?: string | null;
};
export function ChapterIntelHeader({ chapterQuery, levelHint }: Props) {
if (!chapterQuery) return null;
const level = normalizeLevel(levelHint);
const results = searchChapters(chapterQuery, { level, limit: 1 });
if (results.length === 0) return null;
const { chapter, level: foundLevel, subjectSlug } = results[0];
const profile = getExamProfile(chapter);
const priorityVariant: "green" | "amber" | "violet" | "red" =
profile.priority === "critical" ? "red"
: profile.priority === "important" ? "amber"
: profile.priority === "moderate" ? "violet"
: "neutral" as "violet";
const priorityLabel =
profile.priority === "critical" ? "Exam critical"
: profile.priority === "important" ? "High priority"
: profile.priority === "moderate" ? "Worth studying"
: "Low priority";
return (
<div className="rounded-xl border border-white/[0.07] bg-white/[0.025] px-4 py-3.5">
{/* Top row: chapter identity */}
<div className="flex flex-wrap items-center gap-2">
<span className="text-[12px] font-semibold text-white/70">{chapter.name}</span>
<span className="text-white/25">Β·</span>
<span className="text-[11px] text-white/40">{foundLevel} {subjectSlug.charAt(0).toUpperCase() + subjectSlug.slice(1)}</span>
</div>
{/* Badge row */}
<div className="mt-2.5 flex flex-wrap gap-1.5">
{/* Priority */}
<IntelChip variant={priorityVariant}>
<Flame className="size-2.5" />
{priorityLabel}
</IntelChip>
{/* PYQ frequency */}
<IntelChip variant={chapter.pyqFrequency === "very_high" || chapter.pyqFrequency === "high" ? "amber" : "neutral"}>
<Star className="size-2.5" />
{pyqLabel(chapter)}
</IntelChip>
{/* Difficulty */}
<IntelChip variant={chapter.difficultyScore <= 3 ? "green" : "neutral"}>
{difficultyLabel(chapter)}
</IntelChip>
{/* Study time */}
<IntelChip variant="neutral">
<Clock className="size-2.5" />
{chapter.estimatedMinutes} min
</IntelChip>
{/* Formulas count */}
{chapter.formulas && chapter.formulas.length > 0 ? (
<IntelChip variant="violet">
<BookMarked className="size-2.5" />
{chapter.formulas.length} formula{chapter.formulas.length !== 1 ? "s" : ""}
</IntelChip>
) : null}
{/* Last night priority */}
{chapter.lastNightPriority >= 4 ? (
<IntelChip variant="green">
<AlertTriangle className="size-2.5" />
Study tonight
</IntelChip>
) : null}
</div>
{/* Insight line */}
<p className="mt-2 text-[11.5px] leading-relaxed text-white/40">
{profile.action}
{chapter.examWeightageMarks
? ` Β· ~${chapter.examWeightageMarks} marks in theory paper`
: null}
</p>
{/* Prerequisites */}
{chapter.prerequisites.length > 0 ? (
<div className="mt-2.5 flex flex-wrap items-center gap-1.5">
<span className="text-[10.5px] text-white/28">Before this, revise:</span>
{chapter.prerequisites.map((prereq) => (
<span
key={prereq}
className="rounded-full border border-white/[0.06] bg-white/[0.03] px-2 py-0.5 text-[10.5px] text-white/40"
>
{prereq}
</span>
))}
</div>
) : null}
{/* Top formulas preview */}
{chapter.formulas && chapter.formulas.length > 0 ? (
<div className="mt-2.5 flex flex-wrap gap-1.5">
{chapter.formulas.slice(0, 3).map((f) => (
<code
key={f}
className="rounded border border-amber-400/15 bg-amber-400/5 px-2 py-0.5 font-mono text-[10.5px] text-amber-400/80"
>
{f}
</code>
))}
{chapter.formulas.length > 3 ? (
<span className="text-[10.5px] text-white/28">+{chapter.formulas.length - 3} more</span>
) : null}
</div>
) : null}
</div>
);
}