Download apps/web/src/components/generate/TestBlueprintCard.tsx from Atmint/cpns: direct link, hf CLI and curl.
- Browser
- Download file 8.9 kB
-
https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/generate/TestBlueprintCard.tsx
- Command line
-
hf download hf://spaces/Atmint/cpns/apps/web/src/components/generate/TestBlueprintCard.tsx
-
curl -L -o TestBlueprintCard.tsx https://huggingface.co/spaces/Atmint/cpns/resolve/main/apps/web/src/components/generate/TestBlueprintCard.tsx
8.9 kB
| import { Button } from "@pram/ui/components/button"; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@pram/ui/components/card"; | |
| import { MaterialIcon } from "@/components/ui/MaterialIcon"; | |
| import { EXAM_TYPES, SECTIONS } from "@/lib/generate-constants"; | |
| interface TestBlueprintCardProps { | |
| examType: string; | |
| selectedSections: string[]; | |
| selectedFormats: string[]; | |
| questionCount: number; | |
| weaknessAlign: number; | |
| mode: "quick" | "agentic"; | |
| setMode: (mode: "quick" | "agentic") => void; | |
| activeCount: number; | |
| maxParallel: number; | |
| generatePending: boolean; | |
| hasKey: boolean; | |
| error: string | null; | |
| onGenerate: () => void; | |
| disableQuick?: boolean; | |
| } | |
| export function TestBlueprintCard({ | |
| examType, | |
| selectedSections, | |
| selectedFormats, | |
| questionCount, | |
| weaknessAlign, | |
| mode, | |
| setMode, | |
| activeCount, | |
| maxParallel, | |
| generatePending, | |
| hasKey, | |
| error, | |
| onGenerate, | |
| onDismissError, | |
| disableQuick, | |
| }: TestBlueprintCardProps & { onDismissError?: () => void }) { | |
| const sectionNames = selectedSections | |
| .map((id) => SECTIONS.find((s) => s.id === id)?.name) | |
| .filter(Boolean); | |
| return ( | |
| <div className="lg:col-span-4 sticky top-8"> | |
| <Card className="bg-[var(--pure-white)] border-2 border-[var(--oat-border)] clay-shadow rounded-[var(--radius-xl)]"> | |
| <CardHeader> | |
| <div className="flex items-center gap-3"> | |
| <MaterialIcon name="analytics" className="text-[var(--matcha-600)]" /> | |
| <CardTitle className="font-headline text-xl text-[var(--clay-black)]">Test Blueprint</CardTitle> | |
| </div> | |
| </CardHeader> | |
| <CardContent className="space-y-5"> | |
| <div className="flex justify-between items-center pb-4 border-b border-[var(--oat-border)]"> | |
| <span className="text-[var(--warm-charcoal)]">Ujian</span> | |
| <span className="font-bold text-[var(--clay-black)]"> | |
| {EXAM_TYPES.find((t) => t.id === examType)?.name} | |
| </span> | |
| </div> | |
| <div className="flex justify-between items-center pb-4 border-b border-[var(--oat-border)]"> | |
| <span className="text-[var(--warm-charcoal)]">Section</span> | |
| <span className="font-bold text-[var(--clay-black)] text-right"> | |
| {sectionNames.join(" + ")} | |
| </span> | |
| </div> | |
| <div className="flex justify-between items-center pb-4 border-b border-[var(--oat-border)]"> | |
| <span className="text-[var(--warm-charcoal)]">Jumlah Soal</span> | |
| <span className="font-bold text-[var(--clay-black)]">{questionCount} Soal</span> | |
| </div> | |
| <div className="flex justify-between items-center pb-4 border-b border-[var(--oat-border)]"> | |
| <span className="text-[var(--warm-charcoal)]">Format</span> | |
| <span className="font-bold text-[var(--clay-black)]">{selectedFormats.length} Jenis</span> | |
| </div> | |
| <div className="flex justify-between items-center pb-4 border-b border-[var(--oat-border)]"> | |
| <span className="text-[var(--warm-charcoal)]">Estimasi Durasi</span> | |
| <span className="font-bold text-[var(--clay-black)]">{questionCount * 2} Menit</span> | |
| </div> | |
| {/* AI Confidence Score */} | |
| <div className="bg-[var(--oat-light)] rounded-[var(--radius-lg)] p-6 flex flex-col items-center gap-4 relative overflow-hidden"> | |
| <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-[var(--matcha-600)] to-transparent" /> | |
| <span className="text-xs font-label uppercase tracking-widest text-[var(--warm-charcoal)] font-bold"> | |
| AI Confidence Score | |
| </span> | |
| <div className="relative flex items-center justify-center"> | |
| <svg className="w-32 h-32 transform -rotate-90"> | |
| <circle className="text-[var(--warm-silver)]" cx="64" cy="64" fill="transparent" r="56" stroke="currentColor" strokeWidth="8" /> | |
| <circle | |
| className="text-[var(--matcha-600)]" | |
| cx="64" | |
| cy="64" | |
| fill="transparent" | |
| r="56" | |
| stroke="currentColor" | |
| strokeDasharray="351.85" | |
| strokeDashoffset={351.85 - (351.85 * (weaknessAlign / 100))} | |
| strokeWidth="8" | |
| /> | |
| </svg> | |
| <span className="absolute text-3xl font-headline font-extrabold text-[var(--clay-black)]">{Math.round(weaknessAlign)}%</span> | |
| </div> | |
| <p className="text-[11px] text-center text-[var(--warm-charcoal)] px-2 leading-tight"> | |
| Probabilitas tinggi untuk mengatasi blindspot linguistik utama. | |
| </p> | |
| </div> | |
| {/* Mode Toggle */} | |
| <div className="flex gap-1 p-1 rounded-[var(--radius-lg)] bg-[var(--oat-light)]"> | |
| <button | |
| onClick={() => !disableQuick && setMode("quick")} | |
| disabled={disableQuick} | |
| className={`flex-1 py-2.5 px-3 rounded-[var(--radius-md)] text-sm font-semibold transition-all flex items-center justify-center gap-2 min-h-[44px] ${ | |
| disableQuick | |
| ? "bg-[var(--oat-light)] text-[var(--warm-silver)] cursor-not-allowed opacity-50" | |
| : mode === "quick" | |
| ? "bg-[var(--pure-white)] text-[var(--clay-black)] clay-shadow" | |
| : "text-[var(--warm-charcoal)] hover:text-[var(--clay-black)]" | |
| }`} | |
| > | |
| <MaterialIcon name="flash_on" className="text-sm" /> | |
| Quick | |
| </button> | |
| <button | |
| onClick={() => setMode("agentic")} | |
| className={`flex-1 py-2.5 px-3 rounded-[var(--radius-md)] text-sm font-semibold transition-all flex items-center justify-center gap-2 min-h-[44px] ${ | |
| mode === "agentic" | |
| ? "bg-[var(--pure-white)] text-[var(--clay-black)] clay-shadow" | |
| : "text-[var(--warm-charcoal)] hover:text-[var(--clay-black)]" | |
| }`} | |
| > | |
| <MaterialIcon name="psychology" className="text-sm" /> | |
| Agentic | |
| </button> | |
| </div> | |
| {disableQuick && ( | |
| <p className="text-xs text-[var(--warm-charcoal)] flex items-center gap-1"> | |
| <MaterialIcon name="info" className="text-xs shrink-0" /> | |
| Reading/Writing membutuhkan Agentic mode (min 20 soal) | |
| </p> | |
| )} | |
| {mode === "agentic" && ( | |
| <div className="p-3 rounded-[var(--radius-md)] bg-[var(--badge-blue-bg)] border-2 border-[var(--badge-blue-bg)] text-xs text-[var(--badge-blue-text)]"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <MaterialIcon name="info" className="text-sm" /> | |
| <span className="font-semibold">Mode Agentic</span> | |
| </div> | |
| <p>Multi-step validation: passage → validate → questions → self-check → quality score. Lebih lambat tapi kualitas lebih terjamin.</p> | |
| </div> | |
| )} | |
| {/* Primary CTA */} | |
| <Button | |
| className="w-full py-5 rounded-[var(--radius-lg)] bg-[var(--clay-black)] text-[var(--pure-white)] font-bold text-lg flex items-center justify-center gap-3 clay-shadow clay-hover hover:bg-[var(--warm-charcoal)] transition-all active:scale-95 h-auto min-h-[56px]" | |
| onClick={onGenerate} | |
| disabled={activeCount >= maxParallel || generatePending || selectedFormats.length === 0 || !hasKey} | |
| > | |
| <MaterialIcon name="auto_awesome" className="group-hover:rotate-12 transition-transform" /> | |
| {activeCount >= maxParallel | |
| ? `Maks ${maxParallel} Paralel` | |
| : activeCount > 0 | |
| ? mode === "agentic" | |
| ? "Agentic..." | |
| : "Generating..." | |
| : "Generate & Launch"} | |
| </Button> | |
| {error && ( | |
| <div className="p-4 rounded-[var(--radius-md)] bg-[var(--pomegranate-400)]/10 text-[var(--pomegranate-400)] text-sm border-2 border-[var(--pomegranate-400)]/20"> | |
| <div className="flex items-start gap-2"> | |
| <MaterialIcon name="error" className="text-sm shrink-0 mt-0.5" /> | |
| <span className="flex-1">{error}</span> | |
| {onDismissError && ( | |
| <button | |
| onClick={onDismissError} | |
| className="shrink-0 p-1 rounded hover:bg-[var(--pomegranate-400)]/10 transition-colors" | |
| aria-label="Tutup error" | |
| > | |
| <MaterialIcon name="close" className="text-sm" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } | |