Files
nextcraft/apps/web/components/learner/proctor-banner.tsx
T
CIAgent 88a1dab810 docs(milestone): complete v0.2-ai-tutor-architecture
---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).
2026-09-11 17:34:50 +00:00

60 lines
2.2 KiB
TypeScript

'use client';
import { AgentStreamPanel } from './agent-stream-panel';
import { ShieldCheck } from 'lucide-react';
interface IntegritySignal {
signal_type: string;
severity: 'low' | 'medium' | 'high';
note: string;
}
interface ProctorAssessment {
scenario_id: string;
signals: IntegritySignal[];
intervention: string;
summary: string;
}
const SEVERITY_STYLES: Record<string, string> = {
low: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800',
medium: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800',
high: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800',
};
/**
* Proctor integrity banner — supportive, coaching-shaped (never punitive).
*/
export function ProctorBanner({ scenarioId }: { scenarioId: string }) {
return (
<AgentStreamPanel
title="Proctor — integrity support"
endpoint="/v1/proctor/signals"
body={{ scenario_id: scenarioId }}
emptyHint="Run the Proctor to review this session's integrity signals."
renderJson={(data) => {
const assessment = data as unknown as ProctorAssessment;
return (
<div className="flex flex-col gap-3">
<p className="text-xs text-slate-600 dark:text-slate-300">{assessment.summary}</p>
<div className="flex flex-wrap gap-1.5">
{assessment.signals.map((s, i) => (
<span
key={i}
className={`inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs ${SEVERITY_STYLES[s.severity] ?? SEVERITY_STYLES.low}`}
title={s.note}
>
<ShieldCheck className="h-3 w-3" />
{s.signal_type} · {s.severity}
</span>
))}
</div>
<p className="rounded-md bg-primary-50 px-3 py-2 text-xs text-primary-800 dark:bg-primary-900/30 dark:text-primary-200">
<strong>Suggested next step:</strong> {assessment.intervention}
</p>
</div>
);
}}
/>
);
}