Deploy Space app files only
Browse files- README.md +168 -13
- app.py +557 -0
- requirements.txt +12 -0
README.md
CHANGED
|
@@ -1,13 +1,168 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# KnowLedge
|
| 2 |
+
|
| 3 |
+
KnowLedge is a local-first learning verification platform built for the Gemma 4 for Good hackathon.
|
| 4 |
+
|
| 5 |
+
It helps students turn borrowed understanding into real mastery. The system tracks concepts that appear in pasted work, guides the learner through Socratic clearing sessions, verifies understanding with multimodal checks, and shares only privacy-preserving aggregates with instructors.
|
| 6 |
+
|
| 7 |
+
## Why This Project Exists
|
| 8 |
+
|
| 9 |
+
Most AI learning tools optimize for speed. KnowLedge optimizes for retention and accountability.
|
| 10 |
+
|
| 11 |
+
The core idea is simple:
|
| 12 |
+
|
| 13 |
+
- If a student copies a concept without understanding it, the concept is logged as learning debt.
|
| 14 |
+
- If the student can explain it clearly in their own words, the concept is cleared.
|
| 15 |
+
- If the explanation still fails integrity checks, the concept stays under review.
|
| 16 |
+
|
| 17 |
+
The result is a system that uses AI to force better learning rather than passive answer collection.
|
| 18 |
+
|
| 19 |
+
## What Judges Should Look For
|
| 20 |
+
|
| 21 |
+
- A polished desktop/web experience with Ledger, Progress, Reports, and Help views.
|
| 22 |
+
- Offline-first design using local SQLite, Ollama, and ChromaDB.
|
| 23 |
+
- Privacy-preserving instructor sync that shares only concept-level summaries.
|
| 24 |
+
- Integrity checks that combine session fingerprints, anti-spoof signals, and multimodal verification.
|
| 25 |
+
- A separate Expo mobile companion app with the same product model.
|
| 26 |
+
|
| 27 |
+
## Product Overview
|
| 28 |
+
|
| 29 |
+
KnowLedge has three main workflows:
|
| 30 |
+
|
| 31 |
+
1. Scout detects concepts in pasted material and logs them to the ledger.
|
| 32 |
+
2. Sage runs a Socratic clearing session until the student can explain the concept themselves.
|
| 33 |
+
3. Lens checks handwritten or visual work and helps flag concepts that still need review.
|
| 34 |
+
|
| 35 |
+
There is also a weekly instructor sync path that exports only anonymous, aggregate concept data.
|
| 36 |
+
|
| 37 |
+
## Repository Structure
|
| 38 |
+
|
| 39 |
+
- `knowledge/` - FastAPI backend, orchestration, sync, integrity, RAG, and templates.
|
| 40 |
+
- `knowledge-mobile/` - Expo + React Native companion app.
|
| 41 |
+
- `cdt_vectorstore/` - Local ChromaDB persistence for curriculum context.
|
| 42 |
+
- `knowledge_dashboard.html` - Standalone dashboard prototype.
|
| 43 |
+
- `requirements.txt` - Python dependencies for the backend.
|
| 44 |
+
|
| 45 |
+
## Backend Setup
|
| 46 |
+
|
| 47 |
+
### 1. Create a Python environment
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
python3 -m venv .venv
|
| 51 |
+
source .venv/bin/activate
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 2. Install backend dependencies
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
pip install -r requirements.txt
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### 3. Start Ollama
|
| 61 |
+
|
| 62 |
+
KnowLedge expects a local Ollama server.
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
ollama serve
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
If you are using the default Gemma tags, make sure the model names in `knowledge/config.py` exist in your local Ollama install.
|
| 69 |
+
|
| 70 |
+
### 4. Run the app
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
python -m uvicorn knowledge.main:app --host 127.0.0.1 --port 8000
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Open these views in a browser:
|
| 77 |
+
|
| 78 |
+
- `http://127.0.0.1:8000/ledger`
|
| 79 |
+
- `http://127.0.0.1:8000/progress`
|
| 80 |
+
- `http://127.0.0.1:8000/reports`
|
| 81 |
+
- `http://127.0.0.1:8000/help`
|
| 82 |
+
|
| 83 |
+
### 5. Optional: load curriculum material into ChromaDB
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
python -m knowledge.vectorize path/to/course_material.pdf
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
This creates local retrieval context for Sage responses.
|
| 90 |
+
|
| 91 |
+
## Mobile Companion
|
| 92 |
+
|
| 93 |
+
The mobile app lives in `knowledge-mobile/` and is separate from the backend.
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
cd knowledge-mobile
|
| 97 |
+
npm install
|
| 98 |
+
npx expo start
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
If you want a device build:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
npm run android
|
| 105 |
+
npm run ios
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
The mobile app supports three runtime modes:
|
| 109 |
+
|
| 110 |
+
- `on_device_full`
|
| 111 |
+
- `on_device_scout`
|
| 112 |
+
- `server_only`
|
| 113 |
+
|
| 114 |
+
The native Gemma bridge is scaffolded for future Android and iOS implementation.
|
| 115 |
+
|
| 116 |
+
## Live Demo For Kaggle
|
| 117 |
+
|
| 118 |
+
The best judge-facing option is a Hugging Face Space running the Streamlit demo in `app.py`.
|
| 119 |
+
|
| 120 |
+
Why this works well:
|
| 121 |
+
|
| 122 |
+
- It gives you a public HTTPS link with zero setup for judges.
|
| 123 |
+
- The Space can boot automatically when opened, so there is no manual server start step for reviewers.
|
| 124 |
+
- You can connect it to your hosted backend with a single environment variable, or let it fall back to demo data.
|
| 125 |
+
|
| 126 |
+
Recommended setup:
|
| 127 |
+
|
| 128 |
+
1. Create a new Hugging Face Space.
|
| 129 |
+
2. Select the **Gradio** SDK (industry standard for ML demo UIs).
|
| 130 |
+
3. Point the Space to this repository's `app.py`.
|
| 131 |
+
4. Add `gradio` to the dependencies file, which is already done.
|
| 132 |
+
5. If you want live backend data, set a Space secret named `KNOWLEDGE_API_URL` to your deployed backend URL.
|
| 133 |
+
6. Share the Space URL in your Kaggle submission as the live demo link.
|
| 134 |
+
|
| 135 |
+
If you want the Space to show live backend state, keep your FastAPI app deployed separately and set `KNOWLEDGE_API_URL` to that public endpoint. If you want a self-contained demo, the Space still works in fallback mode with sample data.
|
| 136 |
+
|
| 137 |
+
## Configuration
|
| 138 |
+
|
| 139 |
+
Most backend settings live in `knowledge/config.py` and can also be overridden with environment variables.
|
| 140 |
+
|
| 141 |
+
Important values include:
|
| 142 |
+
|
| 143 |
+
- `OLLAMA_HOST`
|
| 144 |
+
- `SCOUT_MODEL`
|
| 145 |
+
- `SAGE_MODEL`
|
| 146 |
+
- `LENS_MODEL`
|
| 147 |
+
- `DB_PATH`
|
| 148 |
+
- `CHROMA_PATH`
|
| 149 |
+
- `SYNC_ON_WIFI_ONLY`
|
| 150 |
+
|
| 151 |
+
The Expo app has its own config in `knowledge-mobile/app.config.js` and `knowledge-mobile/app.json`.
|
| 152 |
+
|
| 153 |
+
## Testing
|
| 154 |
+
|
| 155 |
+
Run the backend tests with:
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
python -m pytest
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
There are also lightweight smoke scripts in the repository root for quick local checks.
|
| 162 |
+
|
| 163 |
+
## Notes for Reviewers
|
| 164 |
+
|
| 165 |
+
- The project is intentionally offline-first.
|
| 166 |
+
- Sync only shares aggregate concept data.
|
| 167 |
+
- The mobile native inference layer is scaffolded, not fully implemented.
|
| 168 |
+
- The main app is in the `knowledge/` package; the mobile app is an independent companion workspace.
|
app.py
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import httpx
|
| 6 |
+
import pandas as pd
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 10 |
+
# Sample Data (Fallback when backend is unavailable)
|
| 11 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 12 |
+
|
| 13 |
+
SAMPLE_LEDGER: List[Dict[str, Any]] = [
|
| 14 |
+
{"concept": "Recursion base case", "status": "on_loan", "confidence": 0.92, "source_text": "Borrowed from AI explanation"},
|
| 15 |
+
{"concept": "Binary search invariants", "status": "clear", "confidence": 0.88, "source_text": "Explained in own words"},
|
| 16 |
+
{"concept": "Gradient descent", "status": "persists", "confidence": 0.79, "source_text": "Needs another clearing session"},
|
| 17 |
+
{"concept": "RAG retrieval", "status": "on_loan", "confidence": 0.84, "source_text": "Added during prompt work"},
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
SAMPLE_HEATMAP = [
|
| 21 |
+
{"concept": "Recursion base case", "count": 9},
|
| 22 |
+
{"concept": "Binary search invariants", "count": 4},
|
| 23 |
+
{"concept": "Gradient descent", "count": 7},
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
SAMPLE_METRICS = {
|
| 27 |
+
"active": 2,
|
| 28 |
+
"cleared": 1,
|
| 29 |
+
"persists": 1,
|
| 30 |
+
"debt_score": 67,
|
| 31 |
+
"pending_sync": 0,
|
| 32 |
+
"spoof_attempts": 3,
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 37 |
+
# Backend Integration
|
| 38 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 39 |
+
|
| 40 |
+
def backend_snapshot(base_url: str) -> Optional[Dict[str, Any]]:
|
| 41 |
+
"""Fetch live data from the knowledge backend API."""
|
| 42 |
+
base = base_url.rstrip("/")
|
| 43 |
+
try:
|
| 44 |
+
with httpx.Client(timeout=6.0) as client:
|
| 45 |
+
state = client.get(f"{base}/api/state", timeout=6.0).json()
|
| 46 |
+
sync = client.get(f"{base}/api/sync/status", timeout=6.0).json()
|
| 47 |
+
integrity = client.get(f"{base}/api/integrity/report", timeout=6.0).json()
|
| 48 |
+
return {"state": state, "sync": sync, "integrity": integrity}
|
| 49 |
+
except Exception:
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def scout_demo(pasted_text: str) -> Tuple[str, str]:
|
| 54 |
+
"""Simulate Scout tagging concepts from pasted text."""
|
| 55 |
+
if not pasted_text.strip():
|
| 56 |
+
return "", "Paste some text to see Scout extract concepts."
|
| 57 |
+
|
| 58 |
+
extracted_concepts = []
|
| 59 |
+
keywords = ["recursion", "binary search", "gradient descent", "rag", "algorithm", "model", "training", "inference"]
|
| 60 |
+
for keyword in keywords:
|
| 61 |
+
if keyword.lower() in pasted_text.lower():
|
| 62 |
+
extracted_concepts.append(keyword.capitalize())
|
| 63 |
+
|
| 64 |
+
if not extracted_concepts:
|
| 65 |
+
extracted_concepts = ["Learning (inferred from context)"]
|
| 66 |
+
|
| 67 |
+
result = f"**Scout detected {len(extracted_concepts)} concept(s):**\n\n"
|
| 68 |
+
for concept in extracted_concepts:
|
| 69 |
+
result += f"- {concept} (confidence: ~85%)\n"
|
| 70 |
+
|
| 71 |
+
status = f"✅ Tagged {len(extracted_concepts)} concept(s) → added to your ledger"
|
| 72 |
+
return result, status
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def sage_demo(concept: str, user_response: str) -> str:
|
| 76 |
+
"""Simulate Sage Socratic dialogue."""
|
| 77 |
+
if not concept.strip():
|
| 78 |
+
return "Enter a concept to start a clearing session."
|
| 79 |
+
|
| 80 |
+
if not user_response.strip():
|
| 81 |
+
return f"🦉 **Sage**: Let's talk about {concept}. Can you explain it in your own words?"
|
| 82 |
+
|
| 83 |
+
quality_score = len(user_response.split()) / 10
|
| 84 |
+
if quality_score < 2:
|
| 85 |
+
return f"🦉 **Sage**: That's a start. But can you go deeper? What makes {concept} special or different?"
|
| 86 |
+
elif quality_score < 4:
|
| 87 |
+
return f"🦉 **Sage**: Good effort! Now, why would someone use {concept} in practice? When does it matter?"
|
| 88 |
+
else:
|
| 89 |
+
return f"✅ **Sage**: Excellent! You've clearly understood {concept}. This concept is now **CLEARED** in your ledger."
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_ledger_display() -> pd.DataFrame:
|
| 93 |
+
"""Return the current ledger as a DataFrame for display."""
|
| 94 |
+
backend_url = os.environ.get("KNOWLEDGE_API_URL", "").strip()
|
| 95 |
+
snapshot = backend_snapshot(backend_url) if backend_url else None
|
| 96 |
+
ledger = snapshot["state"]["debts"] if snapshot else SAMPLE_LEDGER
|
| 97 |
+
|
| 98 |
+
df = pd.DataFrame(ledger)
|
| 99 |
+
if not df.empty:
|
| 100 |
+
df = df[["concept", "status", "confidence"]]
|
| 101 |
+
return df
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def get_metrics() -> Tuple[int, int, int, int]:
|
| 105 |
+
"""Return key metrics: active, cleared, persists, debt_score."""
|
| 106 |
+
backend_url = os.environ.get("KNOWLEDGE_API_URL", "").strip()
|
| 107 |
+
snapshot = backend_snapshot(backend_url) if backend_url else None
|
| 108 |
+
ledger = snapshot["state"]["debts"] if snapshot else SAMPLE_LEDGER
|
| 109 |
+
|
| 110 |
+
active = sum(1 for row in ledger if row.get("status") in {"on_loan", "persists"})
|
| 111 |
+
cleared = sum(1 for row in ledger if row.get("status") in {"clear", "owned"})
|
| 112 |
+
persists = sum(1 for row in ledger if row.get("status") == "persists")
|
| 113 |
+
debt_score = round(((active + persists) / max(len(ledger), 1)) * 100) if ledger else 0
|
| 114 |
+
|
| 115 |
+
return active, cleared, persists, debt_score
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 119 |
+
# Gradio Interface
|
| 120 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 121 |
+
|
| 122 |
+
with gr.Blocks(
|
| 123 |
+
title="KnowLedge | Gemma 4 for Good",
|
| 124 |
+
theme=gr.themes.Soft(
|
| 125 |
+
primary_hue="slate",
|
| 126 |
+
secondary_hue="amber",
|
| 127 |
+
),
|
| 128 |
+
) as demo:
|
| 129 |
+
|
| 130 |
+
gr.Markdown(
|
| 131 |
+
"""
|
| 132 |
+
# 🦉 KnowLedge
|
| 133 |
+
|
| 134 |
+
**A local-first learning verification system for the Gemma 4 for Good hackathon.**
|
| 135 |
+
|
| 136 |
+
KnowLedge turns pasted AI-assisted work into a guided mastery loop:
|
| 137 |
+
- **Scout** extracts concepts from text
|
| 138 |
+
- **Sage** clears them through Socratic dialogue
|
| 139 |
+
- **Lens** verifies understanding with integrity checks
|
| 140 |
+
- **Reports** share only anonymous aggregates with instructors
|
| 141 |
+
"""
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
with gr.Row():
|
| 145 |
+
active, cleared, persists, debt_score = get_metrics()
|
| 146 |
+
|
| 147 |
+
with gr.Column(scale=1):
|
| 148 |
+
gr.Markdown(f"### 📊 Active\n\n**{active}** concepts on loan or persisting")
|
| 149 |
+
with gr.Column(scale=1):
|
| 150 |
+
gr.Markdown(f"### ✅ Cleared\n\n**{cleared}** owned concepts")
|
| 151 |
+
with gr.Column(scale=1):
|
| 152 |
+
gr.Markdown(f"### 🎯 Debt Score\n\n**{debt_score}%** (lower is better)")
|
| 153 |
+
with gr.Column(scale=1):
|
| 154 |
+
gr.Markdown(f"### 🛡️ Integrity\n\n**{SAMPLE_METRICS['spoof_attempts']}** spoof signals detected")
|
| 155 |
+
|
| 156 |
+
with gr.Tabs():
|
| 157 |
+
|
| 158 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 159 |
+
# Tab 1: Scout Demo
|
| 160 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 161 |
+
with gr.TabItem("🔍 Scout — Extract Concepts"):
|
| 162 |
+
gr.Markdown(
|
| 163 |
+
"""
|
| 164 |
+
**Scout** automatically finds concepts inside pasted text. Try pasting an explanation or code snippet below.
|
| 165 |
+
"""
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
with gr.Row():
|
| 169 |
+
with gr.Column(scale=2):
|
| 170 |
+
pasted_text = gr.Textbox(
|
| 171 |
+
label="Paste AI-assisted work or notes here",
|
| 172 |
+
placeholder="e.g., 'Recursion works by dividing a problem into smaller subproblems until reaching a base case...'",
|
| 173 |
+
lines=6,
|
| 174 |
+
)
|
| 175 |
+
with gr.Column(scale=1):
|
| 176 |
+
scout_btn = gr.Button("🚀 Run Scout", size="lg")
|
| 177 |
+
|
| 178 |
+
scout_output = gr.Markdown("Paste something to get started.")
|
| 179 |
+
scout_status = gr.Textbox(label="Status", interactive=False, value="Ready.")
|
| 180 |
+
|
| 181 |
+
scout_btn.click(
|
| 182 |
+
scout_demo,
|
| 183 |
+
inputs=[pasted_text],
|
| 184 |
+
outputs=[scout_output, scout_status],
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 188 |
+
# Tab 2: Sage Demo
|
| 189 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 190 |
+
with gr.TabItem("🦉 Sage — Socratic Clearing"):
|
| 191 |
+
gr.Markdown(
|
| 192 |
+
"""
|
| 193 |
+
**Sage** guides you through a Socratic clearing session. Pick a concept and explain it in your own words.
|
| 194 |
+
"""
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
concept_input = gr.Textbox(
|
| 198 |
+
label="Concept to clear",
|
| 199 |
+
placeholder="e.g., 'Binary Search'",
|
| 200 |
+
value="Recursion",
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
response_input = gr.Textbox(
|
| 204 |
+
label="Your explanation",
|
| 205 |
+
placeholder="Explain the concept in your own words. Be as detailed as you can.",
|
| 206 |
+
lines=4,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
sage_btn = gr.Button("💭 Get Sage Response", size="lg")
|
| 210 |
+
sage_output = gr.Markdown()
|
| 211 |
+
|
| 212 |
+
sage_btn.click(
|
| 213 |
+
sage_demo,
|
| 214 |
+
inputs=[concept_input, response_input],
|
| 215 |
+
outputs=[sage_output],
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 219 |
+
# Tab 3: Live Ledger
|
| 220 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 221 |
+
with gr.TabItem("📖 Live Ledger"):
|
| 222 |
+
gr.Markdown(
|
| 223 |
+
"""
|
| 224 |
+
This is your concept ledger. Every concept you study is tracked here with its status:
|
| 225 |
+
- **on_loan**: You pasted it but haven't cleared it yet.
|
| 226 |
+
- **clear**: You explained it to Sage and passed.
|
| 227 |
+
- **persists**: Lens found gaps, needs another session.
|
| 228 |
+
"""
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
ledger_df = get_ledger_display()
|
| 232 |
+
ledger_table = gr.Dataframe(
|
| 233 |
+
value=ledger_df,
|
| 234 |
+
interactive=False,
|
| 235 |
+
wrap=True,
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
refresh_btn = gr.Button("🔄 Refresh Ledger")
|
| 239 |
+
refresh_btn.click(
|
| 240 |
+
lambda: get_ledger_display(),
|
| 241 |
+
outputs=[ledger_table],
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 245 |
+
# Tab 4: Architecture & Setup
|
| 246 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 247 |
+
with gr.TabItem("🏗️ Architecture"):
|
| 248 |
+
gr.Markdown(
|
| 249 |
+
"""
|
| 250 |
+
## System Design
|
| 251 |
+
|
| 252 |
+
**Backend Stack:**
|
| 253 |
+
- FastAPI for the web server
|
| 254 |
+
- SQLite for concept ledger persistence
|
| 255 |
+
- ChromaDB for curriculum context (RAG)
|
| 256 |
+
- Ollama for local Gemma inference
|
| 257 |
+
- Privacy-preserving sync with concept-level aggregates only
|
| 258 |
+
|
| 259 |
+
**Key Features:**
|
| 260 |
+
- Offline-first (no cloud dependency)
|
| 261 |
+
- Session fingerprinting to detect gaming behavior
|
| 262 |
+
- Anti-spoof scoring on Lens uploads
|
| 263 |
+
- No student identifiers in instructor reports
|
| 264 |
+
|
| 265 |
+
## Quick Local Setup
|
| 266 |
+
|
| 267 |
+
```bash
|
| 268 |
+
python3 -m venv .venv
|
| 269 |
+
source .venv/bin/activate
|
| 270 |
+
pip install -r requirements.txt
|
| 271 |
+
|
| 272 |
+
# Terminal 1: Start Ollama
|
| 273 |
+
ollama serve
|
| 274 |
+
|
| 275 |
+
# Terminal 2: Start the backend
|
| 276 |
+
python -m uvicorn knowledge.main:app --host 127.0.0.1 --port 8000
|
| 277 |
+
|
| 278 |
+
# Optional: Load curriculum material
|
| 279 |
+
python -m knowledge.vectorize path/to/course.pdf
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
## Deploy on Hugging Face Spaces
|
| 283 |
+
|
| 284 |
+
1. Create a new Space with **Gradio** SDK
|
| 285 |
+
2. Push this repo to the Space
|
| 286 |
+
3. Set `KNOWLEDGE_API_URL` environment variable (optional, for live backend)
|
| 287 |
+
4. Share the Space URL as your Kaggle demo link
|
| 288 |
+
"""
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 292 |
+
# Tab 5: For Judges
|
| 293 |
+
# ─────────────────────────────────────────────────────────────────────
|
| 294 |
+
with gr.TabItem("🎯 For Kaggle Judges"):
|
| 295 |
+
gr.Markdown(
|
| 296 |
+
"""
|
| 297 |
+
## What Makes KnowLedge Different
|
| 298 |
+
|
| 299 |
+
Most AI tutoring tools are **answer machines**. KnowLedge is a **verification system**.
|
| 300 |
+
|
| 301 |
+
### The Problem
|
| 302 |
+
- Students copy AI-generated code without understanding
|
| 303 |
+
- Traditional quizzes can be gamed with a second AI
|
| 304 |
+
- Instructors have no way to detect this pattern
|
| 305 |
+
|
| 306 |
+
### The KnowLedge Solution
|
| 307 |
+
- **Scout** logs every concept you borrow from AI
|
| 308 |
+
- **Sage** forces you to explain it yourself before you own it
|
| 309 |
+
- **Lens** checks handwritten work for logic gaps
|
| 310 |
+
- **Integrity** fingerprints your session to catch repeat gaming
|
| 311 |
+
- **Sync** gives instructors only concept-level aggregates (privacy-first)
|
| 312 |
+
|
| 313 |
+
### Why This Matters
|
| 314 |
+
- Fixes a **real problem** in modern education
|
| 315 |
+
- Uses **Gemma 4** for both extraction and dialogue
|
| 316 |
+
- Runs **fully offline** for maximum privacy
|
| 317 |
+
- Provides a **zero-setup** public demo (this Space)
|
| 318 |
+
|
| 319 |
+
### Key Metrics
|
| 320 |
+
- **Debt Score**: How much of your work is still "borrowed"
|
| 321 |
+
- **Spoof Attempts**: Session fingerprinting detects copy-paste patterns
|
| 322 |
+
- **Integrity Signals**: Anti-gaming scoring from Lens
|
| 323 |
+
|
| 324 |
+
This is what judges should look for. Not an answering machine. A **learning enforcer**.
|
| 325 |
+
"""
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
# Launch the Gradio interface
|
| 330 |
+
if __name__ == "__main__":
|
| 331 |
+
demo.launch()
|
| 332 |
+
:root {
|
| 333 |
+
--bg: #f5f0e8;
|
| 334 |
+
--panel: #ffffff;
|
| 335 |
+
--ink: #1c1a14;
|
| 336 |
+
--muted: #7a7565;
|
| 337 |
+
--teal: #0e7a6e;
|
| 338 |
+
--amber: #d4820a;
|
| 339 |
+
--coral: #c94a3a;
|
| 340 |
+
--rule: rgba(28, 26, 20, 0.12);
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.block-container {
|
| 344 |
+
padding-top: 2rem;
|
| 345 |
+
padding-bottom: 2rem;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.hero {
|
| 349 |
+
background: linear-gradient(135deg, rgba(14,122,110,0.10), rgba(212,130,10,0.10));
|
| 350 |
+
border: 1px solid var(--rule);
|
| 351 |
+
border-radius: 24px;
|
| 352 |
+
padding: 2rem;
|
| 353 |
+
margin-bottom: 1.25rem;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.hero h1 {
|
| 357 |
+
color: var(--ink);
|
| 358 |
+
font-size: 3rem;
|
| 359 |
+
line-height: 1;
|
| 360 |
+
margin-bottom: 0.5rem;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.hero p {
|
| 364 |
+
color: var(--muted);
|
| 365 |
+
font-size: 1.02rem;
|
| 366 |
+
line-height: 1.6;
|
| 367 |
+
max-width: 58rem;
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
.badge-row {
|
| 371 |
+
display: flex;
|
| 372 |
+
flex-wrap: wrap;
|
| 373 |
+
gap: 0.6rem;
|
| 374 |
+
margin-top: 1rem;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
.badge {
|
| 378 |
+
display: inline-block;
|
| 379 |
+
padding: 0.35rem 0.65rem;
|
| 380 |
+
border-radius: 999px;
|
| 381 |
+
border: 1px solid var(--rule);
|
| 382 |
+
background: rgba(255,255,255,0.72);
|
| 383 |
+
color: var(--ink);
|
| 384 |
+
font-size: 0.85rem;
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
.card {
|
| 388 |
+
background: var(--panel);
|
| 389 |
+
border: 1px solid var(--rule);
|
| 390 |
+
border-radius: 18px;
|
| 391 |
+
padding: 1rem;
|
| 392 |
+
box-shadow: 0 10px 30px rgba(28, 26, 20, 0.06);
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
.metric {
|
| 396 |
+
background: white;
|
| 397 |
+
border: 1px solid var(--rule);
|
| 398 |
+
border-radius: 18px;
|
| 399 |
+
padding: 1rem;
|
| 400 |
+
min-height: 130px;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.metric-label {
|
| 404 |
+
color: var(--muted);
|
| 405 |
+
font-size: 0.82rem;
|
| 406 |
+
text-transform: uppercase;
|
| 407 |
+
letter-spacing: 0.08em;
|
| 408 |
+
margin-bottom: 0.35rem;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.metric-value {
|
| 412 |
+
color: var(--ink);
|
| 413 |
+
font-size: 2rem;
|
| 414 |
+
font-weight: 700;
|
| 415 |
+
line-height: 1;
|
| 416 |
+
margin-bottom: 0.35rem;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.metric-sub {
|
| 420 |
+
color: var(--muted);
|
| 421 |
+
font-size: 0.88rem;
|
| 422 |
+
line-height: 1.4;
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
.judge-box {
|
| 426 |
+
border-left: 4px solid var(--teal);
|
| 427 |
+
padding: 0.85rem 1rem;
|
| 428 |
+
background: rgba(14, 122, 110, 0.06);
|
| 429 |
+
border-radius: 12px;
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.small-note {
|
| 433 |
+
color: var(--muted);
|
| 434 |
+
font-size: 0.92rem;
|
| 435 |
+
line-height: 1.55;
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
.codebox {
|
| 439 |
+
background: #1c1a14;
|
| 440 |
+
color: #f5f0e8;
|
| 441 |
+
border-radius: 14px;
|
| 442 |
+
padding: 1rem;
|
| 443 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
| 444 |
+
font-size: 0.9rem;
|
| 445 |
+
overflow-x: auto;
|
| 446 |
+
}
|
| 447 |
+
</style>
|
| 448 |
+
"""
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
@st.cache_data(show_spinner=False)
|
| 455 |
+
def backend_snapshot(base_url: str) -> Optional[Dict[str, Any]]:
|
| 456 |
+
base = base_url.rstrip("/")
|
| 457 |
+
try:
|
| 458 |
+
with httpx.Client(timeout=6.0) as client:
|
| 459 |
+
state = client.get(f"{base}/api/state").json()
|
| 460 |
+
sync = client.get(f"{base}/api/sync/status").json()
|
| 461 |
+
integrity = client.get(f"{base}/api/integrity/report").json()
|
| 462 |
+
return {"state": state, "sync": sync, "integrity": integrity}
|
| 463 |
+
except Exception:
|
| 464 |
+
return None
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
backend_url = os.environ.get("KNOWLEDGE_API_URL", "").strip()
|
| 468 |
+
space_mode = os.environ.get("SPACE_MODE", "demo").strip().lower()
|
| 469 |
+
snapshot = backend_snapshot(backend_url) if backend_url else None
|
| 470 |
+
use_live_data = snapshot is not None
|
| 471 |
+
ledger = snapshot["state"]["debts"] if use_live_data else SAMPLE_LEDGER
|
| 472 |
+
heatmap = snapshot["state"]["heatmap"] if use_live_data else SAMPLE_HEATMAP
|
| 473 |
+
sync_status = snapshot["sync"] if use_live_data else {"pending_count": SAMPLE_METRICS["pending_sync"], "last_sync": {"status": "sent"}}
|
| 474 |
+
integrity = snapshot["integrity"] if use_live_data else {"spoof_attempts": SAMPLE_METRICS["spoof_attempts"], "sessions_with_signatures": 8, "sessions_total": 10}
|
| 475 |
+
|
| 476 |
+
active = sum(1 for row in ledger if row.get("status") in {"on_loan", "persists"})
|
| 477 |
+
cleared = sum(1 for row in ledger if row.get("status") in {"clear", "owned"})
|
| 478 |
+
persists = sum(1 for row in ledger if row.get("status") == "persists")
|
| 479 |
+
debt_score = round(((active + persists) / max(len(ledger), 1)) * 100) if ledger else 0
|
| 480 |
+
|
| 481 |
+
st.markdown(
|
| 482 |
+
"""
|
| 483 |
+
<div class="hero">
|
| 484 |
+
<h1>KnowLedge</h1>
|
| 485 |
+
<p>
|
| 486 |
+
A local-first learning verification system for the Gemma 4 for Good hackathon.
|
| 487 |
+
KnowLedge turns pasted AI-assisted work into a guided mastery loop: Scout extracts concepts,
|
| 488 |
+
Sage clears them through Socratic dialogue, and Lens verifies understanding with integrity checks.
|
| 489 |
+
</p>
|
| 490 |
+
<div class="badge-row">
|
| 491 |
+
<span class="badge">FastAPI backend</span>
|
| 492 |
+
<span class="badge">SQLite + ChromaDB</span>
|
| 493 |
+
<span class="badge">Ollama + Gemma</span>
|
| 494 |
+
<span class="badge">Privacy-preserving sync</span>
|
| 495 |
+
<span class="badge">Expo companion app</span>
|
| 496 |
+
</div>
|
| 497 |
+
</div>
|
| 498 |
+
""",
|
| 499 |
+
unsafe_allow_html=True,
|
| 500 |
+
)
|
| 501 |
+
|
| 502 |
+
if use_live_data:
|
| 503 |
+
st.success("Live backend connected. This Space can be used as a zero-setup demo front door.")
|
| 504 |
+
else:
|
| 505 |
+
if backend_url:
|
| 506 |
+
st.warning(f"Backend URL set, but the app could not reach {backend_url}. Showing demo data instead.")
|
| 507 |
+
else:
|
| 508 |
+
st.info("Demo mode is active. Add KNOWLEDGE_API_URL in Hugging Face Spaces to connect to your hosted backend.")
|
| 509 |
+
|
| 510 |
+
col1, col2, col3, col4, col5 = st.columns(5)
|
| 511 |
+
with col1:
|
| 512 |
+
st.markdown(f'<div class="metric"><div class="metric-label">Active Concepts</div><div class="metric-value">{active}</div><div class="metric-sub">currently on loan or persisting</div></div>', unsafe_allow_html=True)
|
| 513 |
+
with col2:
|
| 514 |
+
st.markdown(f'<div class="metric"><div class="metric-label">Cleared</div><div class="metric-value">{cleared}</div><div class="metric-sub">concepts owned by the student</div></div>', unsafe_allow_html=True)
|
| 515 |
+
with col3:
|
| 516 |
+
st.markdown(f'<div class="metric"><div class="metric-label">Debt Score</div><div class="metric-value">{debt_score}%</div><div class="metric-sub">lower is better</div></div>', unsafe_allow_html=True)
|
| 517 |
+
with col4:
|
| 518 |
+
st.markdown(f'<div class="metric"><div class="metric-label">Pending Sync</div><div class="metric-value">{sync_status.get("pending_count", 0)}</div><div class="metric-sub">weekly aggregate queue</div></div>', unsafe_allow_html=True)
|
| 519 |
+
with col5:
|
| 520 |
+
st.markdown(f'<div class="metric"><div class="metric-label">Spoof Signals</div><div class="metric-value">{integrity.get("spoof_attempts", 0)}</div><div class="metric-sub">anti-gaming checks</div></div>', unsafe_allow_html=True)
|
| 521 |
+
|
| 522 |
+
left, right = st.columns([1.15, 0.85], gap="large")
|
| 523 |
+
|
| 524 |
+
with left:
|
| 525 |
+
tabs = st.tabs(["Live Demo", "Architecture", "How to Run", "Judge Notes"])
|
| 526 |
+
|
| 527 |
+
with tabs[0]:
|
| 528 |
+
st.subheader("What judges can test")
|
| 529 |
+
st.write("KnowLedge is designed to show a complete learning-verification loop, not just a chatbot.")
|
| 530 |
+
st.markdown(
|
| 531 |
+
"""
|
| 532 |
+
1. Paste a concept or AI-assisted explanation.
|
| 533 |
+
2. Scout extracts the concepts and logs them to the ledger.
|
| 534 |
+
3. Sage asks clarifying questions until the concept is cleared.
|
| 535 |
+
4. Lens and integrity checks keep the workflow honest.
|
| 536 |
+
5. Reports share only anonymous aggregates.
|
| 537 |
+
"""
|
| 538 |
+
)
|
| 539 |
+
demo_rows = pd.DataFrame(ledger)[["concept", "status", "confidence"]] if ledger else pd.DataFrame(columns=["concept", "status", "confidence"])
|
| 540 |
+
st.dataframe(demo_rows, use_container_width=True, hide_index=True)
|
| 541 |
+
|
| 542 |
+
with tabs[1]:
|
| 543 |
+
st.subheader("Product Architecture")
|
| 544 |
+
st.markdown(
|
| 545 |
+
"""
|
| 546 |
+
- **Scout** finds concepts inside pasted text.
|
| 547 |
+
- **Sage** runs the Socratic clearing loop.
|
| 548 |
+
- **Lens** checks handwritten or visual reasoning.
|
| 549 |
+
- **Sync** exports only concept-level aggregates.
|
| 550 |
+
- **Integrity** uses fingerprints and anti-spoof scoring.
|
| 551 |
+
"""
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
# Launch the Gradio interface
|
| 556 |
+
if __name__ == "__main__":
|
| 557 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
python-multipart
|
| 4 |
+
gradio
|
| 5 |
+
httpx
|
| 6 |
+
ollama
|
| 7 |
+
chromadb
|
| 8 |
+
pdfplumber
|
| 9 |
+
pillow
|
| 10 |
+
pandas
|
| 11 |
+
jinja2
|
| 12 |
+
python-dotenv
|