diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..8ca1ac9 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(/usr/local/bin/node --version)", + "Bash(/opt/homebrew/bin/node --version)", + "Read(//opt/homebrew/bin/**)", + "Bash(curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh)", + "Bash(bash)", + "Bash(curl -LsSf https://astral.sh/uv/install.sh)", + "Bash(sh)", + "Bash(python3 -c \"import secrets; print\\(secrets.token_hex\\(32\\)\\)\")" + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dadd495..0494f0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [main, rag] pull_request: - branches: [main] + branches: [main, rag] jobs: backend: @@ -38,22 +38,22 @@ jobs: with: python-version: "3.11" cache: pip - cache-dependency-path: services/ai/requirements.txt + cache-dependency-path: services/ai/pyproject.toml - name: Install dependencies - run: pip install -r requirements.txt + run: pip install -e ".[dev]" - name: Type check (mypy) - run: mypy . + run: mypy app env: DATABASE_URL: postgresql://bitcoin_academy:bitcoin_academy@localhost:5432/bitcoin_academy - SECRET_KEY: ci-secret-key-32-chars-minimum!! + SECRET_KEY: CI-AUTH-JWT-KEY-FOR-PIPELINE-ONLY-32! - name: Run tests (pytest) run: pytest env: DATABASE_URL: postgresql://bitcoin_academy:bitcoin_academy@localhost:5432/bitcoin_academy - SECRET_KEY: ci-secret-key-32-chars-minimum!! + SECRET_KEY: CI-AUTH-JWT-KEY-FOR-PIPELINE-ONLY-32! ENVIRONMENT: development frontend: @@ -82,6 +82,8 @@ jobs: - name: Lint run: npm run lint + env: + NEXT_PUBLIC_API_BASE_URL: http://localhost:8000/api - name: Run tests (Jest) run: npm test -- --ci --passWithNoTests diff --git a/.gitignore b/.gitignore index a91efc3..a4c55ef 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,9 @@ htmlcov/ .dmypy.json dmypy.json +# Test coverage reports +apps/web/coverage/ + # Node.js / npm / yarn / pnpm node_modules/ npm-debug.log* @@ -77,8 +80,10 @@ out/ .env.*.local .env.production.local -# Lock files (optional - uncomment if you want to exclude) +# Lock files — ignore root-level, but track app lock files for reproducible CI package-lock.json +!apps/web/package-lock.json +!workers/qvac-service/package-lock.json # yarn.lock # pnpm-lock.yaml @@ -103,7 +108,7 @@ temp/ # Docker .dockerignore -docker-compose.override.yml +docker-compose.local.yml # IDE config .editorconfig @@ -150,3 +155,8 @@ docs/ *.jsonl parsed_output/ chroma_db/ + +# AI service runtime artifacts +services/ai/uploads/ +services/ai/qvac_ingest/ +services/ai/rag_test_results*.json diff --git a/BitPolito-Academy-UI/academy.html b/BitPolito-Academy-UI/academy.html deleted file mode 100644 index fa4216b..0000000 --- a/BitPolito-Academy-UI/academy.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - -BitPolito Academy - - - - - - - - - - - - - -
- - - - - - - - - - diff --git a/BitPolito-Academy-UI/assets/bitpolito-logo.svg b/BitPolito-Academy-UI/assets/bitpolito-logo.svg deleted file mode 100644 index 7bcccce..0000000 --- a/BitPolito-Academy-UI/assets/bitpolito-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/BitPolito-Academy-UI/assets/icon-github.svg b/BitPolito-Academy-UI/assets/icon-github.svg deleted file mode 100644 index 327d9c0..0000000 --- a/BitPolito-Academy-UI/assets/icon-github.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/BitPolito-Academy-UI/assets/icon-moon.svg b/BitPolito-Academy-UI/assets/icon-moon.svg deleted file mode 100644 index b18b6a0..0000000 --- a/BitPolito-Academy-UI/assets/icon-moon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/BitPolito-Academy-UI/assets/icon-sun.svg b/BitPolito-Academy-UI/assets/icon-sun.svg deleted file mode 100644 index 33d663c..0000000 --- a/BitPolito-Academy-UI/assets/icon-sun.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/BitPolito-Academy-UI/src/app2.jsx b/BitPolito-Academy-UI/src/app2.jsx deleted file mode 100644 index 5b89fc6..0000000 --- a/BitPolito-Academy-UI/src/app2.jsx +++ /dev/null @@ -1,88 +0,0 @@ -// App shell — routing, dark mode, tweaks panel. - -const { useState: _u4, useEffect: _e4 } = React; - -const TWEAK_DEFAULS = /*EDITMODE-BEGIN*/{ - "studyLayout": "split", - "density": "comfortable", - "showInspect": true, - "accent": "blue" -}/*EDITMODE-END*/; - -function App() { - const [route, setRoute] = _u4("home"); - const [dark, setDark] = _u4(false); - const tk = (window.useTweaks || (() => [TWEAK_DEFAULS, () => {}]))(TWEAK_DEFAULS); - const [tweaks, setTweak] = tk; - - _e4(() => { - document.documentElement.classList.toggle("dark", dark); - }, [dark]); - - // Density - _e4(() => { - const r = document.documentElement; - if (tweaks.density === "compact") r.style.fontSize = "14.5px"; - else r.style.fontSize = "16px"; - }, [tweaks.density]); - - window.__nav = setRoute; - - let screen; - if (route === "home") screen = setRoute("workspace")} onCreate={() => setRoute("workspace")} />; - else if (route === "workspace") screen = setRoute("source")} onStartStudy={() => setRoute("study")} />; - else if (route === "source") screen = setRoute("study")} />; - else if (route === "study") screen = ; - - return ( -
- setDark(d => !d)} /> -
{screen}
-
-
- BitPolito · Academy - local-first · open-source - built on qvac SDK - v0.1 · MVP preview -
-
- - {window.TweaksPanel ? ( - - - setTweak("studyLayout", v)} - /> -

- Split is recommended for the MVP — most students read source first, output second. - Power flips the order for users who think output-first. -

-
- - setTweak("density", v)} /> - - - setDark(v)} label="Dark mode" /> - - -
- setRoute("home")} /> - setRoute("workspace")} /> - setRoute("source")} /> - setRoute("study")} /> -
-
-
- ) : null} -
- ); -} - -ReactDOM.createRoot(document.getElementById("root")).render(); diff --git a/BitPolito-Academy-UI/src/atoms.jsx b/BitPolito-Academy-UI/src/atoms.jsx deleted file mode 100644 index a3076f6..0000000 --- a/BitPolito-Academy-UI/src/atoms.jsx +++ /dev/null @@ -1,196 +0,0 @@ -// Shared atoms for BitPolito Academy. - -const { useState, useEffect, useRef, useMemo } = React; - -// --- Brand monogram (the pixelated bitpolito glyph), simplified to "BP·A" -function BrandMark({ className = "" }) { - return ( - { e.preventDefault(); window.__nav && window.__nav("home"); }} - className={`inline-flex items-center gap-3 ${className}`}> - {/* Bit-pixel logo (simplified) */} - - - - - - - - - - - - - - - - - - - - - BitPolito · Academy - - - ); -} - -// --- Status chip -function StatusChip({ state, progress }) { - const map = { - queued: { dot: "#7a7f9a", label: "queued", ring: "#7a7f9a" }, - uploading: { dot: "#a55a00", label: "uploading", ring: "#a55a00" }, - uploaded: { dot: "#1a7f3a", label: "uploaded", ring: "#1a7f3a" }, - processing: { dot: "#a55a00", label: "processing", ring: "#a55a00" }, - indexed: { dot: "#1a7f3a", label: "indexed", ring: "#1a7f3a" }, - failed: { dot: "#b3261e", label: "failed", ring: "#b3261e" }, - }; - const m = map[state] || map.queued; - const animated = state === "processing" || state === "uploading"; - return ( - - - {m.label} - {animated && progress != null - ? {Math.round(progress * 100)}% - : null} - - ); -} - -// --- Striped placeholder (used for course covers, slide thumbnails, figures) -function Stripes({ label, className = "", aspect = "16/10" }) { - return ( -
-
- - {label} - -
-
-
-
-
-
- ); -} - -// --- Section header (mono caption + rule) -function SectionHead({ left, right, className = "" }) { - return ( -
-
- {left} -
- {right ?
{right}
: null} -
- ); -} - -// --- Thin progress bar -function ProgressBar({ value = 0, animated = true }) { - return ( -
-
-
- ); -} - -// --- Top app bar -function TopBar({ active, onNav, dark, onToggleDark }) { - const tabs = [ - { id: "home", label: "Courses" }, - { id: "workspace", label: "Workspace" }, - { id: "source", label: "Source" }, - { id: "study", label: "Study" }, - ]; - return ( -
-
- - - -
-
- - - - Search courses, docs, passages… - ⌘K -
- -
- LC -
-
-
-
- ); -} - -// --- Breadcrumb -function Crumbs({ items }) { - return ( -
- {items.map((it, i) => ( - - {i > 0 ? / : null} - {it} - - ))} -
- ); -} - -// --- Empty / error / loading micro-states -function EmptyBlock({ title, sub, action }) { - return ( -
-
-
{title}
-
{sub}
- {action ?
{action}
: null} -
- ); -} - -// --- Citation pill (inline, hoverable) -function Cite({ id, children, onHover, onClick, active }) { - return ( - onHover && onHover(id)} - onMouseLeave={() => onHover && onHover(null)} - onClick={() => onClick && onClick(id)} - className={"inline cursor-pointer transition-colors " + (active ? "cite-hl" : "")} - style={{ borderBottom: "1px dashed currentColor", paddingBottom: "1px" }}> - {children} - [{id.replace("ev-", "")}] - - ); -} - -Object.assign(window, { BrandMark, StatusChip, Stripes, SectionHead, ProgressBar, TopBar, Crumbs, EmptyBlock, Cite }); diff --git a/BitPolito-Academy-UI/src/data.jsx b/BitPolito-Academy-UI/src/data.jsx deleted file mode 100644 index 2f0f860..0000000 --- a/BitPolito-Academy-UI/src/data.jsx +++ /dev/null @@ -1,326 +0,0 @@ -// Mock domain model for BitPolito Academy MVP. -// Kept verbose & technical-flavoured to read credibly inside the UI. - -const ACADEMY_DATA = { - user: { name: "L. Conti", handle: "lconti", role: "M.Sc. ICT Eng" }, - - courses: [ - { - id: "info-theory", - code: "01TWZSM", - title: "Information Theory & Coding", - term: "2025/26 · Sem II", - lecturer: "Prof. M. Re", - docs: 14, - indexed: 12, - processing: 1, - failed: 1, - lastTouched: "2 hours ago", - cover: "stripes", - pinned: true, - }, - { - id: "applied-crypto", - code: "02LSEOV", - title: "Applied Cryptography", - term: "2025/26 · Sem II", - lecturer: "Prof. A. Basile", - docs: 9, - indexed: 9, - processing: 0, - failed: 0, - lastTouched: "yesterday", - cover: "stripes", - pinned: true, - }, - { - id: "distsys", - code: "01UDUOV", - title: "Distributed Systems", - term: "2025/26 · Sem II", - lecturer: "Prof. G. Marchetto", - docs: 22, - indexed: 20, - processing: 0, - failed: 2, - lastTouched: "3 days ago", - cover: "stripes", - }, - { - id: "stoch-proc", - code: "01TVNOV", - title: "Stochastic Processes", - term: "2024/25 · Sem I", - lecturer: "Prof. R. Garello", - docs: 6, - indexed: 6, - processing: 0, - failed: 0, - lastTouched: "last week", - cover: "stripes", - }, - { - id: "comp-arch", - code: "01OUZOV", - title: "Computer Architecture", - term: "2024/25 · Sem I", - lecturer: "Prof. S. Di Carlo", - docs: 11, - indexed: 11, - processing: 0, - failed: 0, - lastTouched: "Apr 2025", - cover: "stripes", - }, - ], - - // Documents inside the *active* course (info-theory). - documents: [ - { - id: "doc-01", - name: "L05 — Source Coding Theorem.pdf", - kind: "slides", - pages: 42, - sizeKB: 3120, - uploadedAt: "Apr 28", - state: "indexed", - chunks: 318, - parser: { ok: 0.97, ocr: false, lang: "en" }, - }, - { - id: "doc-02", - name: "L06 — Channel Capacity & AWGN.pdf", - kind: "slides", - pages: 38, - sizeKB: 2740, - uploadedAt: "Apr 28", - state: "indexed", - chunks: 286, - parser: { ok: 0.96, ocr: false, lang: "en" }, - }, - { - id: "doc-03", - name: "Cover & Thomas — Ch.7 (excerpt).pdf", - kind: "textbook", - pages: 26, - sizeKB: 4180, - uploadedAt: "Apr 27", - state: "indexed", - chunks: 412, - parser: { ok: 0.99, ocr: false, lang: "en" }, - }, - { - id: "doc-04", - name: "Past exam — 2024-07-12.pdf", - kind: "exam", - pages: 6, - sizeKB: 480, - uploadedAt: "Apr 27", - state: "indexed", - chunks: 47, - parser: { ok: 0.94, ocr: true, lang: "it" }, - }, - { - id: "doc-05", - name: "Lecture notes — typicality (handwritten).pdf", - kind: "notes", - pages: 12, - sizeKB: 8820, - uploadedAt: "Apr 30", - state: "processing", - progress: 0.62, - stage: "chunking → embed", - chunks: null, - parser: { ok: 0.81, ocr: true, lang: "en" }, - }, - { - id: "doc-06", - name: "L07 — Rate-Distortion (annotated).pdf", - kind: "slides", - pages: 31, - sizeKB: 3990, - uploadedAt: "Apr 30", - state: "uploading", - progress: 0.38, - chunks: null, - parser: null, - }, - { - id: "doc-07", - name: "Tutorial 04 — Huffman exercises.pdf", - kind: "exercises", - pages: 4, - sizeKB: 220, - uploadedAt: "Apr 30", - state: "failed", - error: "Parser: malformed xref table at byte 184902. Re-export from source.", - chunks: null, - parser: null, - }, - { - id: "doc-08", - name: "L04 — Entropy & Mutual Information.pdf", - kind: "slides", - pages: 36, - sizeKB: 2510, - uploadedAt: "Apr 24", - state: "indexed", - chunks: 274, - parser: { ok: 0.98, ocr: false, lang: "en" }, - }, - ], - - // The currently-open document, used by the source viewer. - // We model 4 "pages" (slide thumbnails) with parsed structure. - openDoc: { - id: "doc-01", - name: "L05 — Source Coding Theorem.pdf", - pages: 42, - activePage: 17, - outline: [ - { id: "s1", label: "1. Information & entropy", page: 2 }, - { id: "s2", label: "2. Typical sequences", page: 9 }, - { id: "s3", label: "3. AEP — Asymptotic Equipartition", page: 14 }, - { id: "s4", label: "4. Source coding theorem", page: 17, active: true }, - { id: "s5", label: "5. Achievability proof", page: 22 }, - { id: "s6", label: "6. Converse", page: 28 }, - { id: "s7", label: "7. Examples — Bernoulli source", page: 33 }, - { id: "s8", label: "8. Limits & extensions", page: 38 }, - ], - // Slide-17 parsed content for preview + citation anchoring - parsed: { - title: "Source Coding Theorem (Shannon, 1948)", - bullets: [ - "Statement: For an i.i.d. source with entropy H(X), there exists a uniquely decodable code with rate R ≥ H(X). Conversely, any code with R < H(X) has error probability bounded away from 0 as n → ∞.", - "Construction relies on the typical set T_ε^(n): for n large, P(T_ε^(n)) ≥ 1 − ε and |T_ε^(n)| ≤ 2^{n(H(X)+ε)}.", - "Encoder: index sequences in T_ε^(n) with ⌈n(H+ε)⌉ bits; assign a single error symbol to atypical sequences.", - "Decoding error: P_e ≤ ε + 2^{−nε/2} for sufficiently large n (Chebyshev on log-likelihood).", - ], - formula: "n · H(X) ≤ E[ ℓ(C) ] ≤ n · H(X) + 1", - cap: "Fig. 5.4 — Operational meaning of H(X) as the minimum rate.", - }, - }, - - studyActions: [ - { id: "explain", label: "Explain concept", sub: "Step-by-step derivation", shortcut: "E", glyph: "Σ" }, - { id: "summarize", label: "Summarize section", sub: "Compressed, source-anchored", shortcut: "S", glyph: "≡" }, - { id: "passages", label: "Retrieve passages", sub: "Top-k from evidence pack", shortcut: "R", glyph: "⌖" }, - { id: "open", label: "Open questions", sub: "Conceptual prompts to test depth",shortcut: "O", glyph: "?" }, - { id: "quiz", label: "Quiz questions", sub: "Multiple-choice with rationale", shortcut: "Q", glyph: "▢" }, - { id: "oral", label: "Oral-exam prompts", sub: "Adversarial, edge-case driven", shortcut: "L", glyph: "◉" }, - { id: "derive", label: "Derive / prove", sub: "Proof scaffolding from sources", shortcut: "D", glyph: "∂" }, - { id: "compare", label: "Compare definitions", sub: "Reconcile across sources", shortcut: "C", glyph: "⇌" }, - ], - - // Mock evidence pack for the active study request. - evidence: [ - { - id: "ev-1", - doc: "doc-01", docName: "L05 — Source Coding Theorem.pdf", - anchor: "p.17 · §4", - score: 0.918, - rerank: 0.871, - kind: "slide", - preview: "…the typical set T_ε^(n) satisfies P(T_ε^(n)) ≥ 1 − ε and |T_ε^(n)| ≤ 2^{n(H(X)+ε)}, which is the cornerstone of the achievability part…", - }, - { - id: "ev-2", - doc: "doc-03", docName: "Cover & Thomas — Ch.7 (excerpt).pdf", - anchor: "p.62 · Thm 5.4.1", - score: 0.902, - rerank: 0.864, - kind: "textbook", - preview: "…the source coding theorem states that any rate above the entropy is achievable, and any rate below entropy is not. Equivalently, expected codeword length L satisfies H(X) ≤ L < H(X) + 1…", - }, - { - id: "ev-3", - doc: "doc-01", docName: "L05 — Source Coding Theorem.pdf", - anchor: "p.22 · §5", - score: 0.876, - rerank: 0.812, - kind: "slide", - preview: "…achievability proof — encode sequences in the typical set with ⌈n(H+ε)⌉ bits and assign a single fail-symbol to the atypical complement…", - }, - { - id: "ev-4", - doc: "doc-08", docName: "L04 — Entropy & Mutual Information.pdf", - anchor: "p.11 · Def 3.2", - score: 0.788, - rerank: 0.741, - kind: "slide", - preview: "…entropy H(X) = −Σ p(x) log p(x) is the unique (up to scale) measure satisfying continuity, monotonicity and the chain rule…", - }, - { - id: "ev-5", - doc: "doc-04", docName: "Past exam — 2024-07-12.pdf", - anchor: "Q.3 · part (b)", - score: 0.701, - rerank: 0.683, - kind: "exam", - preview: "…compute the minimum expected codeword length for a Bernoulli(p=0.1) source over blocks of length n=4 and compare with H(X)…", - lang: "it", - }, - ], - - // Mock generated answer for the active study request. - generated: { - action: "explain", - query: "Explain the Source Coding Theorem and why the typical set bounds the achievable rate.", - timestamp: "just now", - tokensIn: 4218, - tokensOut: 612, - model: "qvac-rag-mix · sonnet-4 fallback", - body: [ - { - h: "Statement", - p: [ - { t: "For an i.i.d. source with entropy ", c: null }, - { t: "H(X)", c: null, code: true }, - { t: ", a code with rate ", c: null }, - { t: "R > H(X)", c: null, code: true }, - { t: " is achievable, while any rate ", c: null }, - { t: "R < H(X)", c: null, code: true }, - { t: " has error probability bounded away from zero", c: "ev-1" }, - { t: " as block length grows. The bound is tight: ", c: null }, - { t: "H(X) ≤ E[ℓ(C)] < H(X) + 1", c: "ev-2", code: true }, - { t: ".", c: null }, - ], - }, - { - h: "Why the typical set", - p: [ - { t: "The achievability argument hinges on the typical set ", c: null }, - { t: "T_ε^(n)", c: null, code: true }, - { t: ": for large enough ", c: null }, - { t: "n", c: null, code: true }, - { t: ", almost all probability mass concentrates on a vanishingly-small fraction of sequences", c: "ev-1" }, - { t: ". Concretely ", c: null }, - { t: "|T_ε^(n)| ≤ 2^{n(H+ε)}", c: "ev-1", code: true }, - { t: ", which gives a direct construction: index typical sequences with ", c: null }, - { t: "⌈n(H+ε)⌉", c: null, code: true }, - { t: " bits and reserve a single fail-symbol for the atypical complement", c: "ev-3" }, - { t: ".", c: null }, - ], - }, - { - h: "Why R < H(X) fails", - p: [ - { t: "If R < H(X), the code can index at most ", c: null }, - { t: "2^{nR}", c: null, code: true }, - { t: " distinct sequences, but typical sequences number at least ", c: null }, - { t: "2^{n(H−ε)}", c: null, code: true }, - { t: ". For small enough ε the inequality forces a constant fraction of typical sequences to collide, so ", c: null }, - { t: "P_e", c: null, code: true }, - { t: " is bounded below by a non-vanishing constant — see the converse on slide 28", c: "ev-1" }, - { t: ".", c: null }, - ], - }, - ], - flags: [ - { kind: "ok", label: "All claims grounded in 5 passages" }, - { kind: "warn", label: "1 passage in Italian — translated inline" }, - ], - }, -}; - -window.ACADEMY_DATA = ACADEMY_DATA; diff --git a/BitPolito-Academy-UI/src/screens-courses.jsx b/BitPolito-Academy-UI/src/screens-courses.jsx deleted file mode 100644 index b40d826..0000000 --- a/BitPolito-Academy-UI/src/screens-courses.jsx +++ /dev/null @@ -1,213 +0,0 @@ -// Courses screen (Home) + course-creation modal. - -const { useState: _u1 } = React; - -function CoursesScreen({ onOpenCourse, onCreate }) { - const D = window.ACADEMY_DATA; - const [creating, setCreating] = _u1(false); - const [filter, setFilter] = _u1("all"); // all | active | archived - - const courses = D.courses; - - return ( -
- {/* Hero head */} -
-
- -

- Study, grounded in your
own course material. -

-

- Each course is an isolated workspace. Drop in slides, notes, textbooks - and past exams — Academy parses, indexes and keeps every generated answer - anchored to its exact source. -

-
- - - - 5 courses · 62 documents · 1.4k chunks indexed - -
-
- - {/* Right: status snapshot */} -
-
- -
- - - - -
-
- Local-first · all data on device - v0.1 MVP -
-
-
-
- - {/* Filter rail */} -
- {[ - { id: "all", label: "All", n: 5 }, - { id: "active", label: "Active term", n: 3 }, - { id: "archived", label: "Archived", n: 2 }, - ].map(f => ( - - ))} -
sorted · last touched
-
- - {/* Course grid */} -
- {courses.map(c => onOpenCourse(c.id)} />)} - -
- - {creating ? setCreating(false)} onCreate={onCreate} /> : null} -
- ); -} - -function Stat({ n, k, warn }) { - return ( -
-
{n}
-
{k}
-
- ); -} - -function CourseCard({ c, onOpen }) { - const totalState = - c.failed > 0 ? { dot: "#b3261e", label: c.failed + " failed" } - : c.processing>0 ? { dot: "#a55a00", label: c.processing + " processing" } - : { dot: "#1a7f3a", label: "all indexed" }; - - return ( -
-
- {c.code} - {c.pinned ? PINNED : null} -
- -

{c.title}

-
- {c.term} · {c.lecturer} -
- -
- - - 0 || c.processing > 0} /> -
- -
- - - {totalState.label} - - {c.lastTouched} -
-
- ); -} - -function Mini({ n, k, warn }) { - return ( -
-
{n}
-
{k}
-
- ); -} - -function CreateCourseModal({ onClose, onCreate }) { - const [title, setTitle] = _u1(""); - const [code, setCode] = _u1(""); - const [term, setTerm] = _u1("2025/26 · Sem II"); - - return ( -
-
e.stopPropagation()}> -
- - -
-

Create a course workspace

-

- A course is a sealed bucket — its documents, embeddings and outputs never bleed into - other courses. -

- -
- - setTitle(e.target.value)} - className="w-full h-10 px-3 b-hard-1 rounded-md bg-transparent outline-none focus:bg-blue-dark/5 dark:focus:bg-white/10" - placeholder="Information Theory & Coding" /> - -
- - setCode(e.target.value)} - className="w-full h-10 px-3 b-hard-1 rounded-md bg-transparent mono outline-none focus:bg-blue-dark/5 dark:focus:bg-white/10" - placeholder="01TWZSM" /> - - - - -
-
- -
- - ▢ Use empty workspace · ▣ Clone settings from another course - -
- - -
-
-
-
- ); -} - -function Field({ label, hint, children }) { - return ( - - ); -} - -Object.assign(window, { CoursesScreen, CourseCard, CreateCourseModal }); diff --git a/BitPolito-Academy-UI/src/screens-study2.jsx b/BitPolito-Academy-UI/src/screens-study2.jsx deleted file mode 100644 index 191bdc0..0000000 --- a/BitPolito-Academy-UI/src/screens-study2.jsx +++ /dev/null @@ -1,380 +0,0 @@ -// Study workspace — split-pane source/output + evidence drawer. - -const { useState: _u3, useEffect: _e3, useRef: _r3 } = React; - -function StudyScreen({ layoutVariant = "split" }) { - const D = window.ACADEMY_DATA; - const [action, setAction] = _u3("explain"); - const [query, setQuery] = _u3(D.generated.query); - const [running, setRunning] = _u3(false); - const [hovered, setHovered] = _u3(null); - const [activeEv, setActiveEv] = _u3("ev-1"); - const [showInspect, setShowInspect] = _u3(false); - const [evDrawer, setEvDrawer] = _u3(true); - - const runStudy = () => { - setRunning(true); - setTimeout(() => setRunning(false), 1400); - }; - - // Power layout swaps source (right) and output (left) - const sourceLeft = layoutVariant !== "power"; - - return ( -
- {/* Action bar */} -
-
- {/* Action selector */} -
- -
- {D.studyActions.map(a => ( - - ))} -
-
- - {/* Query + scope */} -
- a.id===action)?.label || "")} - right={running ? "running…" : "ready"} /> -