import React, { useEffect, useState } from "react";
import { login, refresh, fetchPacks, fetchAdminPacks, fetchPackValidation, fetchPackProvenance, fetchPackVersions, fetchPackComments, upsertPack, publishPack, governanceAction, addReviewComment, listLearners, createLearner, fetchLearnerState, fetchRecommendations, postEvidence, submitEvaluatorJob, fetchEvaluatorHistory, fetchEvaluatorTrace } from "./api";
import { loadAuth, saveAuth, clearAuth } from "./authStore";
function LoginView({ onAuth }) {
const [username, setUsername] = useState("wesley");
const [password, setPassword] = useState("demo-pass");
const [error, setError] = useState("");
async function doLogin() {
try {
const result = await login(username, password);
saveAuth(result);
onAuth(result);
} catch { setError("Login failed"); }
}
return (
);
}
function NavTabs({ tab, setTab, role }) {
return (
{role === "admin" ? <>
> : null}
);
}
function PackAuthorForm({ value, onChange, onSave }) {
function setField(field, val) { onChange({ ...value, [field]: val }); }
function setCompliance(field, val) { onChange({ ...value, compliance: { ...value.compliance, [field]: val } }); }
return (
);
}
export default function App() {
const [auth, setAuth] = useState(loadAuth());
const [tab, setTab] = useState("learner");
const [packs, setPacks] = useState([]);
const [adminPacks, setAdminPacks] = useState([]);
const [learners, setLearners] = useState([]);
const [selectedLearnerId, setSelectedLearnerId] = useState("wesley-learner");
const [selectedPackId, setSelectedPackId] = useState("");
const [learnerState, setLearnerState] = useState(null);
const [cards, setCards] = useState([]);
const [history, setHistory] = useState([]);
const [selectedTrace, setSelectedTrace] = useState(null);
const [validation, setValidation] = useState(null);
const [provenance, setProvenance] = useState(null);
const [versions, setVersions] = useState([]);
const [comments, setComments] = useState([]);
const [commentText, setCommentText] = useState("Looks structurally plausible.");
const [reviewSummary, setReviewSummary] = useState("Reviewed and ready for next stage.");
const [newLearnerId, setNewLearnerId] = useState("wesley-learner");
const [formPack, setFormPack] = useState({ id: "new-pack", title: "New Pack", subtitle: "Editable governance scaffold", level: "novice-friendly", concepts: [], onboarding: { headline: "Start here", body: "Begin", checklist: [] }, compliance: { sources: 0, attributionRequired: false, shareAlikeRequired: false, noncommercialOnly: false, flags: [] } });
const [message, setMessage] = useState("");
async function refreshAuthToken() {
if (!auth?.refresh_token) return null;
try {
const result = await refresh(auth.refresh_token);
saveAuth(result);
setAuth(result);
return result;
} catch {
clearAuth();
setAuth(null);
return null;
}
}
async function guarded(fn) {
try { return await fn(auth.access_token); }
catch {
const next = await refreshAuthToken();
if (!next) throw new Error("auth failed");
return await fn(next.access_token);
}
}
useEffect(() => {
if (!auth) return;
async function load() {
const p = await guarded((token) => fetchPacks(token));
setPacks(p);
setSelectedPackId((prev) => prev || p[0]?.id || "");
let ls = await guarded((token) => listLearners(token));
if (ls.length === 0) {
await guarded((token) => createLearner(token, selectedLearnerId, selectedLearnerId));
ls = await guarded((token) => listLearners(token));
}
setLearners(ls);
if (auth.role === "admin") {
setAdminPacks(await guarded((token) => fetchAdminPacks(token)));
}
}
load();
}, [auth]);
useEffect(() => {
if (!auth || !selectedLearnerId || !selectedPackId) return;
async function loadStuff() {
setLearnerState(await guarded((token) => fetchLearnerState(token, selectedLearnerId)));
const recs = await guarded((token) => fetchRecommendations(token, selectedLearnerId, selectedPackId));
setCards(recs.cards || []);
setHistory(await guarded((token) => fetchEvaluatorHistory(token, selectedLearnerId)));
if (auth.role === "admin") {
setValidation(await guarded((token) => fetchPackValidation(token, selectedPackId)));
setProvenance(await guarded((token) => fetchPackProvenance(token, selectedPackId)));
setVersions(await guarded((token) => fetchPackVersions(token, selectedPackId)));
setComments(await guarded((token) => fetchPackComments(token, selectedPackId)));
}
}
loadStuff();
}, [auth, selectedLearnerId, selectedPackId]);
async function simulateCard(card) {
await guarded((token) => postEvidence(token, selectedLearnerId, { concept_id: card.conceptId, dimension: "mastery", score: card.scoreHint, confidence_hint: card.confidenceHint, timestamp: new Date().toISOString(), kind: "checkpoint", source_id: `ui-${card.id}` }));
setLearnerState(await guarded((token) => fetchLearnerState(token, selectedLearnerId)));
const recs = await guarded((token) => fetchRecommendations(token, selectedLearnerId, selectedPackId));
setCards(recs.cards || []);
setMessage(card.reward);
}
async function runEvaluator() {
const conceptId = packs.find((p) => p.id === selectedPackId)?.concepts?.[0]?.id || "prior";
await guarded((token) => submitEvaluatorJob(token, selectedLearnerId, { pack_id: selectedPackId, concept_id: conceptId, submitted_text: "This is a moderately detailed learner response intended to trigger a somewhat better prototype evaluator score.", kind: "checkpoint" }));
setTimeout(async () => {
setHistory(await guarded((token) => fetchEvaluatorHistory(token, selectedLearnerId)));
setLearnerState(await guarded((token) => fetchLearnerState(token, selectedLearnerId)));
const recs = await guarded((token) => fetchRecommendations(token, selectedLearnerId, selectedPackId));
setCards(recs.cards || []);
}, 1200);
}
async function createLearnerNow() {
await guarded((token) => createLearner(token, newLearnerId, newLearnerId));
const ls = await guarded((token) => listLearners(token));
setLearners(ls);
setSelectedLearnerId(newLearnerId);
}
async function savePack() {
await guarded((token) => upsertPack(token, { pack: formPack, is_published: false, change_summary: "Submitted from form editor" }));
setAdminPacks(await guarded((token) => fetchAdminPacks(token)));
setPacks(await guarded((token) => fetchPacks(token)));
setMessage("Draft saved");
}
async function togglePublish(packId, isPublished) {
await guarded((token) => publishPack(token, packId, isPublished));
setAdminPacks(await guarded((token) => fetchAdminPacks(token)));
setPacks(await guarded((token) => fetchPacks(token)));
}
async function doGovernance(status) {
await guarded((token) => governanceAction(token, selectedPackId, { status, review_summary: reviewSummary }));
setAdminPacks(await guarded((token) => fetchAdminPacks(token)));
setVersions(await guarded((token) => fetchPackVersions(token, selectedPackId)));
setMessage(`Pack moved to ${status}`);
}
async function addCommentNow() {
const versionNumber = versions[0]?.version_number || 1;
await guarded((token) => addReviewComment(token, selectedPackId, versionNumber, { comment_text: commentText, disposition: "comment" }));
setComments(await guarded((token) => fetchPackComments(token, selectedPackId)));
setMessage("Review comment added");
}
async function loadTrace(jobId) {
setSelectedTrace(await guarded((token) => fetchEvaluatorTrace(token, jobId)));
}
if (!auth) return ;
return (
Didactopus review governance layer
Versioning, review comments, governance states, evaluator traces, and curator-facing pack workflows.
Signed in as {auth.username} ({auth.role})
{message ?
{message}
: null}
{tab === "learner" && (
Learner dashboard
{cards.length ? cards.map((card) => (
{card.title}
{card.minutes} minutes
{card.reward}
{card.reason}
Why this is recommended
{card.why.map((w, idx) => - {w}
)}
)) :
No recommendations available.
}
Learner state snapshot
{JSON.stringify(learnerState, null, 2)}
)}
{tab === "history" && (
Evaluator history
{history.length ? (
| Job | Status | Concept | Score | Trace |
{history.map((row) => (
| {row.job_id} |
{row.status} |
{row.concept_id} |
{row.result_score ?? "-"} |
|
))}
) : No evaluator jobs yet.
}
Evaluator trace
{JSON.stringify(selectedTrace, null, 2)}
)}
{tab === "manage" && (
Existing learners
| Learner ID | Display name | Owner |
{learners.map((row) => (
| {row.learner_id} |
{row.display_name} |
{row.owner_user_id} |
))}
)}
{tab === "admin" && auth.role === "admin" && (
Pack administration
| ID | Version | State | Published | Action |
{adminPacks.map((row) => (
| {row.id} |
{row.current_version} |
{row.governance_state} |
{String(row.is_published)} |
|
))}
)}
{tab === "review" && auth.role === "admin" && (
Governance review
Validation
{JSON.stringify(validation, null, 2)}
Provenance
{JSON.stringify(provenance, null, 2)}
Versions and comments
Pack versions
{JSON.stringify(versions, null, 2)}
Review comments
{JSON.stringify(comments, null, 2)}
)}
);
}