88a1dab810
---ci--- phase: 7 milestone: v0.2 status: complete requirements: covered: [REQ-2-001, REQ-2-002, REQ-2-003, REQ-2-004, REQ-2-005, REQ-2-006, REQ-2-007, REQ-2-008, REQ-2-009, REQ-2-010, REQ-2-011, REQ-2-012] partial: [] ---/ci--- Milestone v0.2 (ai-tutor-architecture) merged to main. Escalation record (audit remediation, durable): P1 executor delegation failed twice (empty subagent results, zero files created); auto-resolved at full autonomy to inline execution with identical plan fidelity (commit 3271373, reflog-only after phase branch squash-delete).
101 lines
3.7 KiB
TypeScript
101 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import { AgentStreamPanel } from './agent-stream-panel';
|
|
import { AlertTriangle, CheckCircle2, CircleDashed } from 'lucide-react';
|
|
|
|
interface CriterionScore {
|
|
criterion_id: string;
|
|
name: string;
|
|
score: number;
|
|
evidence: string;
|
|
}
|
|
|
|
interface RubricScore {
|
|
rubric_id: string;
|
|
artifact_id: string;
|
|
competency_id: string;
|
|
scores: CriterionScore[];
|
|
strengths: string[];
|
|
gaps: string[];
|
|
verdict: string;
|
|
}
|
|
|
|
/**
|
|
* Assessment surface — Assessor rubric output (structured JSON) +
|
|
* Proctor integrity banner. Mock engine inputs; real engines v0.3+.
|
|
*/
|
|
export function AssessorResultsPanel({ artifactId }: { artifactId: string }) {
|
|
return (
|
|
<AgentStreamPanel
|
|
title="Assessor — rubric evaluation"
|
|
endpoint="/v1/assessment/evaluate"
|
|
body={{ artifact_id: artifactId }}
|
|
emptyHint="Run the Assessor to grade this artifact against its rubric."
|
|
renderJson={(data) => {
|
|
const score = data as unknown as RubricScore;
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex items-center gap-2">
|
|
{score.verdict === 'mastered' ? (
|
|
<CheckCircle2 className="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
|
) : score.verdict === 'developing' ? (
|
|
<CircleDashed className="h-5 w-5 text-amber-600 dark:text-amber-400" />
|
|
) : (
|
|
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400" />
|
|
)}
|
|
<span className="text-sm font-semibold capitalize text-slate-900 dark:text-slate-100">
|
|
{score.verdict}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-3">
|
|
{score.scores.map((c) => (
|
|
<div key={c.criterion_id}>
|
|
<div className="mb-1 flex items-center justify-between text-xs">
|
|
<span className="font-medium text-slate-700 dark:text-slate-300">{c.name}</span>
|
|
<span className="text-slate-500 dark:text-slate-400">{c.score}/100</span>
|
|
</div>
|
|
<div className="h-2 overflow-hidden rounded-full bg-slate-200 dark:bg-slate-700">
|
|
<div
|
|
className="h-full rounded-full bg-primary-500 transition-all"
|
|
style={{ width: `${c.score}%` }}
|
|
role="progressbar"
|
|
aria-valuenow={c.score}
|
|
aria-valuemin={0}
|
|
aria-valuemax={100}
|
|
aria-label={c.name}
|
|
/>
|
|
</div>
|
|
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400">{c.evidence}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
<div>
|
|
<h4 className="mb-1 text-xs font-semibold uppercase tracking-wide text-emerald-700 dark:text-emerald-400">
|
|
Strengths
|
|
</h4>
|
|
<ul className="list-inside list-disc text-xs text-slate-600 dark:text-slate-300">
|
|
{score.strengths.map((s) => (
|
|
<li key={s}>{s}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h4 className="mb-1 text-xs font-semibold uppercase tracking-wide text-amber-700 dark:text-amber-400">
|
|
Gaps
|
|
</h4>
|
|
<ul className="list-inside list-disc text-xs text-slate-600 dark:text-slate-300">
|
|
{score.gaps.map((g) => (
|
|
<li key={g}>{g}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}}
|
|
/>
|
|
);
|
|
} |