From d7b7c235a50b166791bf72043ccff9991817a945 Mon Sep 17 00:00:00 2001 From: welsberr Date: Sat, 14 Mar 2026 13:29:55 -0400 Subject: [PATCH] Apply ZIP update: 185-didactopus-course-compliance-ui-prototype.zip [2026-03-14T13:20:21] --- pyproject.toml | 13 +- samples/sources.yaml | 22 +-- webui/index.html | 6 +- webui/package.json | 16 +- webui/src/App.jsx | 337 ++++++++++++++++--------------------------- webui/src/main.jsx | 1 + webui/src/styles.css | 201 ++++++++++++++++++++++---- 7 files changed, 321 insertions(+), 275 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17c47da..eb5549c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,19 +6,10 @@ build-backend = "setuptools.build_meta" name = "didactopus" version = "0.1.0" requires-python = ">=3.10" -dependencies = [ - "pydantic>=2.7", - "pyyaml>=6.0", - "fastapi>=0.115", - "uvicorn>=0.30", - "sqlalchemy>=2.0", - "psycopg[binary]>=3.1", - "passlib[bcrypt]>=1.7", - "python-jose[cryptography]>=3.3" -] +dependencies = ["pydantic>=2.7", "pyyaml>=6.0"] [project.scripts] -didactopus-api = "didactopus.api:main" +didactopus-compliance-demo = "didactopus.course_ingestion_compliance:main" [tool.setuptools.packages.find] where = ["src"] diff --git a/samples/sources.yaml b/samples/sources.yaml index cfafa11..0f366b7 100644 --- a/samples/sources.yaml +++ b/samples/sources.yaml @@ -1,6 +1,6 @@ sources: - - source_id: mit-ocw-bayes-demo - title: Example MIT OpenCourseWare Course Page + - source_id: mit-ocw-bayes + title: Example MIT OpenCourseWare Bayesian Materials url: https://ocw.mit.edu/courses/example-course/ publisher: Massachusetts Institute of Technology creator: MIT OpenCourseWare @@ -8,16 +8,12 @@ sources: license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/ retrieved_at: 2026-03-13 adapted: true - adaptation_notes: > - Didactopus extracted topic structure, concepts, and exercise prompts into a derived domain pack. - attribution_text: > - Derived in part from MIT OpenCourseWare material, used under CC BY-NC-SA 4.0. + attribution_text: Derived in part from MIT OpenCourseWare material used under CC BY-NC-SA 4.0. excluded_from_upstream_license: false exclusion_notes: "" - tags: [mit-ocw, bayes, course] - - source_id: mit-ocw-third-party-note - title: Example Excluded Third-Party Item + - source_id: excluded-figure + title: Example Third-Party Figure url: https://ocw.mit.edu/courses/example-course/pages/lecture-videos/ publisher: Massachusetts Institute of Technology creator: Third-party rights holder @@ -25,10 +21,6 @@ sources: license_url: "" retrieved_at: 2026-03-13 adapted: false - adaptation_notes: "" - attribution_text: > - Referenced only for exclusion tracking; not reused in redistributed Didactopus artifacts. + attribution_text: Tracked for exclusion; not reused in redistributed pack content. excluded_from_upstream_license: true - exclusion_notes: > - This item was flagged as excluded from the OCW Creative Commons license and should not be redistributed as pack content. - tags: [mit-ocw, excluded-content] + exclusion_notes: Figure flagged as excluded from the course-level Creative Commons license. diff --git a/webui/index.html b/webui/index.html index 722e9d2..a3c39a2 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3,8 +3,10 @@ - Didactopus Contribution Management Layer + Didactopus Learner Prototype -
+ +
+ diff --git a/webui/package.json b/webui/package.json index 69118be..040e6f5 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,9 +1,17 @@ { - "name": "didactopus-contribution-ui", + "name": "didactopus-learner-ui", "private": true, "version": "0.1.0", "type": "module", - "scripts": { "dev": "vite", "build": "vite build" }, - "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" }, - "devDependencies": { "vite": "^5.4.0" } + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "vite": "^5.4.0" + } } diff --git a/webui/src/App.jsx b/webui/src/App.jsx index 307ff58..d70fc33 100644 --- a/webui/src/App.jsx +++ b/webui/src/App.jsx @@ -1,243 +1,148 @@ -import React, { useEffect, useState } from "react"; -import { login, refresh, fetchPacks, createContribution, fetchAdminPacks, fetchPackValidation, fetchPackProvenance, fetchPackVersions, fetchPackComments, fetchSubmissions, fetchSubmissionDiff, fetchSubmissionGates, fetchReviewTasks, upsertPack, publishPack, governanceAction, addReviewComment } from "./api"; -import { loadAuth, saveAuth, clearAuth } from "./authStore"; +import React, { useMemo, useState } from "react"; +import { domains } from "./sampleData"; -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"); } - } +function DomainCard({ domain, selected, onSelect }) { return ( -
-
-

Didactopus login

- - - - {error ?
{error}
: null} -
+ + ); +} + +function OnboardingPanel({ onboarding }) { + return ( +
+

First-session onboarding

+

{onboarding.headline}

+

{onboarding.body}

+
    + {onboarding.checklist.map((item, idx) =>
  • {item}
  • )} +
+
+ ); +} + +function NextStepCard({ step }) { + return ( +
+
+
+

{step.title}

+
{step.minutes} minutes
+
+
{step.reward}
+
+

{step.reason}

+
+ Why this is recommended +
    + {step.why.map((item, idx) =>
  • {item}
  • )} +
+
+
); } -function NavTabs({ tab, setTab, role }) { +function MasteryMap({ nodes }) { return ( -
- - {role === "admin" ? <> - - - : null} -
+
+

Visible mastery map

+
+ {nodes.map((node) => ( +
+
{node.label}
+
{node.status}
+
+ ))} +
+
+ ); +} + +function MilestonePanel({ milestones, rewardLabel, progress }) { + return ( +
+

Milestones and rewards

+
+
Mastery progress
+
+
{progress}%
+
+
{rewardLabel}
+
    + {milestones.map((m, idx) =>
  • {m}
  • )} +
+
+ ); +} + +function CompliancePanel({ compliance }) { + return ( +
+

Source attribution and compliance

+
+
Sources
{compliance.sources}
+
Attribution
{compliance.attributionRequired ? "required" : "not required"}
+
Share-alike
{compliance.shareAlikeRequired ? "yes" : "no"}
+
Noncommercial
{compliance.noncommercialOnly ? "yes" : "no"}
+
+
+ {compliance.flags.length ? compliance.flags.map((f) => {f}) : No extra flags} +
+

+ This panel is here so a learner or curator can inspect provenance-sensitive packs without needing to guess what the reuse constraints are. +

+
); } export default function App() { - const [auth, setAuth] = useState(loadAuth()); - const [tab, setTab] = useState("contribute"); - const [packs, setPacks] = useState([]); - const [adminPacks, setAdminPacks] = useState([]); - const [selectedPackId, setSelectedPackId] = useState(""); - const [validation, setValidation] = useState(null); - const [provenance, setProvenance] = useState(null); - const [versions, setVersions] = useState([]); - const [comments, setComments] = useState([]); - const [submissions, setSubmissions] = useState([]); - const [selectedSubmissionId, setSelectedSubmissionId] = useState(null); - const [submissionDiff, setSubmissionDiff] = useState(null); - const [submissionGates, setSubmissionGates] = useState(null); - const [reviewTasks, setReviewTasks] = useState([]); - const [commentText, setCommentText] = useState("Looks structurally plausible."); - const [reviewSummary, setReviewSummary] = useState("Reviewed and ready for next stage."); - const [message, setMessage] = useState(""); - const [contribPack, setContribPack] = useState({ - id: "bayes-pack", - title: "Bayesian Reasoning", - subtitle: "Contributor revision scaffold", - level: "novice-friendly", - concepts: [{ id: "prior", title: "Prior", prerequisites: [], masteryDimension: "mastery", exerciseReward: "Prior badge earned" }], - onboarding: { headline: "Start here", body: "Begin", checklist: [] }, - compliance: { sources: 1, attributionRequired: true, shareAlikeRequired: false, noncommercialOnly: false, flags: [] } - }); - - 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 || ""); - if (auth.role === "admin") { - setAdminPacks(await guarded((token) => fetchAdminPacks(token))); - setSubmissions(await guarded((token) => fetchSubmissions(token))); - setReviewTasks(await guarded((token) => fetchReviewTasks(token))); - } - } - load(); - }, [auth]); - - useEffect(() => { - if (!auth?.role || auth.role !== "admin" || !selectedPackId) return; - async function loadPackReview() { - 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))); - } - loadPackReview(); - }, [auth, selectedPackId]); - - useEffect(() => { - if (!auth?.role || auth.role !== "admin" || !selectedSubmissionId) return; - async function loadSubmission() { - setSubmissionDiff(await guarded((token) => fetchSubmissionDiff(token, selectedSubmissionId))); - setSubmissionGates(await guarded((token) => fetchSubmissionGates(token, selectedSubmissionId))); - } - loadSubmission() - }, [auth, selectedSubmissionId]); - - async function submitContribution() { - const result = await guarded((token) => createContribution(token, { pack: contribPack, submission_summary: "Contributor-submitted revision from UI scaffold" })); - setMessage(`Submission created: ${result.submission_id}`); - } - - 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"); - } - - if (!auth) return ; + const [selectedDomainId, setSelectedDomainId] = useState(domains[0].id); + const domain = useMemo(() => domains.find((d) => d.id === selectedDomainId) || domains[0], [selectedDomainId]); return (
-

Didactopus contribution management layer

-

Contributor submissions, diffs, QA/provenance gates, and reviewer task queue scaffolding.

-
Signed in as {auth.username} ({auth.role})
- {message ?
{message}
: null} -
-
- {auth.role === "admin" ? ( - - ) : ( - - )} - +

Didactopus learner prototype

+

+ Pick a topic, get a clear first session, see your mastery map, and understand why the system suggests each next step. +

- +
+ {domains.map((d) => ( + + ))} +
- {tab === "contribute" && ( -
-
-

Contributor submission

- - - -