An interactive LLM-powered tutor that transforms markdown-based "trainer prompts" into personalized, conversational learning experiences.
TrainingLLM lets content authors write structured markdown files that define questions, hints, and guidance rules. An AI tutor then uses these prompts to teach learners through natural conversation—asking questions, providing progressive hints, detecting cheating, and never revealing answers prematurely.
Three roles:
- Admin: Manage learners (create accounts, generate invite codes), monitor learner progress in real-time, view chat logs, detect cheating flags
- Editor: Create, edit, reorder, and delete lesson files (markdown)
- Learner: Log in with an invite code, chat with the virtual teacher, resume where you left off
# Install dependencies
npm install
cd backend && npm install
cd ../frontend && npm install
# Run everything on http://localhost:3000
cd .. && npm run devThe dev server serves the Vue frontend with hot reload and proxies /api/* to the Next.js backend.
Create a .env file in the project root:
key=your-api-key
baseurl=https://api.openai.com/v1
model=gpt-4
Works with any OpenAI-compatible API (tested with Qwen3-max).
Admin credentials are stored in data/admins.json. Default credentials:
- Username:
admin - Password:
admin123
To add more admins, edit data/admins.json:
[
{ "username": "admin", "password": "admin123", "name": "Administrator" },
{ "username": "teacher", "password": "mypassword", "name": "Teacher Name" }
]Learners log in using invite codes generated by admins. Each learner gets a unique 6-character code. The admin can also share a direct invite link: http://localhost:3000/login?code=ABC123
The admin dashboard (/admin) provides:
- User management: Create learners (name + optional phone), auto-generate invite codes and shareable links
- Progress monitoring: See each learner's status (Not Started / In Progress / Finished) in real-time via WebSocket
- Cheating flags: View escalation flags, cheating attempt counts
- Chat logs: View the complete chat history for any learner, including all questions and answers
- Learners see only the chat interface — no debug tools or editor access
- Progress is saved automatically. Closing the browser and returning resumes from the last position
- The learner's name is injected into the tutor prompt for a personalized experience
- Once the course is complete, the chat becomes read-only — learners can review but not send new messages
Media files (images, videos, audio) for lessons are served from the courses/ directory. To add media to a lesson:
-
Place your media files in the lesson directory:
courses/lesson-1/ ├── meta.json ├── question-1.md ├── question-2.md ├── intro-video.mp4 ← media file └── diagram.png ← media file -
Reference them in your trainer prompt markdown using relative paths:
 
-
Media is served via
GET /api/media/{path}with proper MIME types and caching. Supported formats:- Images: png, jpg, jpeg, gif, webp, svg
- Video: mp4, webm, mov, avi, mkv
- Audio: mp3, ogg, wav, m4a, flac, aac
- Documents: pdf
Each markdown file in courses/ defines one question. Example:
### Question
What is the primary factor that determines if a viewer keeps watching a video?
### Correct Answer
The "hook" — the first few seconds that grab attention. Accept: "hook", "first few seconds", "grabbing attention".
### Hints (give one at a time)
1. Think about your own scrolling behavior on TikTok...
2. Platforms measure scroll-away rate in the first seconds...
3. Starts with H — a fisherman uses one to catch fish.
### Wrong Answer Guidance
- If they say "thumbnail": Good instinct, but that's before they click...
- If they say "quality": Viral videos can be shot on phones...
### After Correct Answer
Great! The hook is crucial. Now let's look at what makes a good hook...The AI tutor reads this as its instruction set—the learner never sees the raw prompt.
The AI tutor uses a multi-step agent loop with structured tools:
- evaluateAnswer: Assesses student responses (correct/wrong/cheating/off-topic)
- updateUserState: Tracks node mastery through a finite state machine
- advanceToNode: Moves through the lesson graph
- generateResponse: Produces the visible response to the student
- escalateError: Flags trolling, prompt injection, or abuse
- Answer is never revealed until max attempts are exhausted
- Cheating attempts (prompt injection) don't count toward attempts
- After max failures, the answer is force-revealed
- Hints are checked by a secondary LLM call to ensure answers aren't leaked
- Progress is tracked in application state, not by the LLM
Admins receive real-time updates via WebSocket when:
- A learner starts a lesson
- A learner answers a question (right or wrong)
- Cheating is detected
- A learner completes the course
| Layer | Technology |
|---|---|
| Backend | Next.js + TypeScript (port 3001) |
| Frontend | Vite + Vue 3 + Tailwind CSS |
| Dev Server | Custom Node server (port 3000) |
| LLM | OpenAI-compatible API |
| Storage | Flat .md files + JSON |
| Auth | Token-based (in-memory) |
| Real-time | WebSocket (/ws/admin) |
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/admin-login |
Admin login (username + password) |
| POST | /api/auth/learner-login |
Learner login (invite code) |
| GET | /api/auth/me |
Get current user info |
| Method | Path | Description |
|---|---|---|
| GET | /api/users |
List all learners |
| POST | /api/users |
Create learner (name, phone) |
| DELETE | /api/users/[id] |
Delete learner |
| Method | Path | Description |
|---|---|---|
| GET | /api/admin/learners |
Learner progress overview |
| GET | /api/admin/learners/[id] |
Learner detail + chat logs |
| Method | Path | Description |
|---|---|---|
| GET | /api/progress |
Get own progress (learner) |
| POST | /api/progress/update |
Update progress position |
| Method | Path | Description |
|---|---|---|
| GET | /api/lessons |
List all lessons |
| POST | /api/lessons |
Create lesson |
| GET/PUT/DELETE | /api/lessons/[id] |
CRUD single lesson |
| GET/POST | /api/lessons/[id]/questions |
List/create questions |
| GET/PUT/DELETE | /api/lessons/[id]/questions/[qId] |
CRUD single question |
| POST | /api/chat/open |
Start question (teacher opening) |
| POST | /api/chat/reply |
Reply to student message |
| GET | /api/media/[...path] |
Serve media files |
trainingLLM/
├── backend/ # Next.js API + Node server
│ ├── server.js # Custom HTTP server (Vite + Next.js + WebSocket)
│ ├── lib/
│ │ ├── auth.ts # Auth, user management, progress
│ │ ├── ws.ts # WebSocket broadcast
│ │ ├── llm.ts # OpenAI client
│ │ ├── lessons.ts # File system CRUD
│ │ ├── fsm/ # FSM generation & state management
│ │ └── agent/ # Agentic tutor loop + tools
│ └── pages/api/ # REST endpoints
├── frontend/ # Vue 3 app
│ └── src/
│ ├── auth.ts # Auth state management
│ ├── router.ts # Vue Router (login/learn/admin)
│ ├── views/
│ │ ├── LoginView.vue # Invite code + admin login
│ │ ├── LearnerView.vue # Chat interface (resume, readonly)
│ │ ├── AdminView.vue # Dashboard + user mgmt + chat logs
│ │ └── EditorView.vue # Lesson/question editor
│ └── components/
├── courses/ # Lesson content (.md + media files)
├── data/ # Runtime data (JSON)
│ ├── admins.json # Admin credentials
│ ├── users.json # Learner accounts
│ └── progress/ # Per-learner progress files
└── .env # API credentials
This project was inspired by MarkdownFlow — the idea that you can "write once, deliver personally." TrainingLLM applies this principle to education: authors write trainer prompts once in plain markdown, and every learner receives a personalized, adaptive tutoring session.
MIT