Αρχιτεκτονική¶
Το Adaptive Learner είναι μια 4-επίπεδη εφαρμογή βασισμένη σε plugins.
┌─────────────────────────────────────────────────────────────┐
│ Frontend React 19 + TypeScript 6 + Vite 8 + │
│ Vitest 4 + Dexie 4 (IndexedDB) + TipTap │
└─────────────────────────────────────────────────────────────┘
↑↓ /api/*
┌─────────────────────────────────────────────────────────────┐
│ Backend FastAPI ^0.136 + SQLAlchemy ^2.0 + │
│ Pydantic v2 + Alembic + Fernet │
└─────────────────────────────────────────────────────────────┘
↑↓ hookspecs
┌─────────────────────────────────────────────────────────────┐
│ PluginForge ^0.10.0 (εξωτερικό PyPI· ταυτοποίηση │
│ μέσω target_application) │
└─────────────────────────────────────────────────────────────┘
↑↓ entry_points
┌─────────────────────────────────────────────────────────────┐
│ Plugins 10 πακέτα στο plugins/ │
│ (ai-{anthropic,openai,gemini}, assessment,│
│ session, tracking, tools, gamification, │
│ anki, notebooklm) │
└─────────────────────────────────────────────────────────────┘
Νέα χαρακτηριστικά ανήκουν ΠΑΝΤΑ σε plugin, εκτός αν αφορούν τον πυρήνα (users / projects / settings / curriculum / topics / lessons / backup / sync / system / import).
Δυαδική αποθήκευση (v0.7.0)¶
Το frontend έχει ένα μοναδικό σημείο επιλογής του αποθηκευτικού
υποστρώματος: getStorage(): IStorageService. Δύο υλοποιήσεις
ικανοποιούν το ίδιο συμβόλαιο:
apiStorage(προεπιλογή): λεπτό περιτύλιγμα γύρω από τοapi/client.tsπου επικοινωνεί με το FastAPI backend.dexieStorage(local-first): πλήρης στοίβα IndexedDB που αντικατοπτρίζει τα 25 μοντέλα SQLAlchemy. Οι κλήσεις ΤΝ εκτελούνται απευθείας από τον browser μέσωstorage/ai-providers.ts.
Το IStorageService εκθέτει 22 namespaces (users, projects,
settings, assessment, session με streaming, tracking, tools,
curricula, topics, lessons, plugins, system, backup, export,
subjects, tags, projectTaxonomy, imports, gamification, anki,
pronunciation, notebooklm). Και οι δύο υλοποιήσεις καλύπτουν
κάθε μέθοδο.
Το factory διαβάζει
localStorage["adaptive-learner.storage_mode"] και μετά
VITE_STORAGE_MODE (ορίζεται στο GH Pages build) και
αφήνει ως προεπιλογή το api. Η εναλλαγή λειτουργιών δεν
γίνεται εν ζωή: η σελίδα Ρυθμίσεων αποθηκεύει την επιλογή
και εμφανίζει ειδοποίηση για επανεκκίνηση.
Τριεπίπεδα μυστικά (v1.20.0 / Phase 34)¶
Κάθε κλήση ΤΝ διατρέχει την αλυσίδα μέσω
services/settings.resolve_api_key:
- Μεταβλητή περιβάλλοντος
ADAPTIVE_LEARNER_<PROVIDER>_API_KEY. ai.<provider>.api_keyστο~/.config/adaptive_learner/secrets.yaml.- Fernet-αποκρυπτογραφημένη στήλη DB.
None— η κλήση ΤΝ εμφανίζει σφάλμα στο UI.
Η απόδοση πηγής αποθηκεύεται στο UserSettingsOut.key_source_*
(enum: env / secrets_yaml / settings / none).
Το UI Ρυθμίσεων απενεργοποιεί Αποθήκευση / Αφαίρεση όταν η
πηγή είναι env ή secrets_yaml.
Η ίδια αλυσίδα εφαρμόζεται και στις παρακάμψεις default_model
ανά πάροχο· το secrets.yaml υπερτερεί της παράκαμψης UI σύμφωνα
με τον σχεδιασμό Phase 34 (η διαμόρφωση αρχείου κερδίζει έναντι
UI για τους προχωρημένους χρήστες).
Δομή plugin¶
plugins/adaptive-learner-plugin-<name>/
adaptive_learner_<name>/
plugin.py # <Name>Plugin(BasePlugin), υλοποιήσεις hooks
routes.py # FastAPI router (αναθέτει σε service functions)
<module>.py # επιχειρηματική λογική
tests/
test_*.py # pytest tests
pyproject.toml # entry point: [project.entry-points."adaptive_learner.plugins"]
- Η κλάση plugin κληρονομεί από
BasePlugin(pluginforge). - Η επιχειρηματική λογική βρίσκεται στα δικά της modules, ΟΧΙ στο routes.py.
- Το routes.py περιέχει μόνο FastAPI endpoints που αναθέτουν.
- Οι hook specs βρίσκονται στο
backend/app/hookspecs.py. - Εξαρτήσεις plugin ως class attribute:
depends_on = ["session"]. - Όλα τα plugins είναι δωρεάν (MIT). Η υποδομή αδειοδότησης
υπάρχει αλλά είναι αδρανής (
LICENSING_ENABLED = False).
Hooks (8 specs στο backend/app/hookspecs.py)¶
| Hook | Πότε | First-result; |
|---|---|---|
get_assessment_questions(lang) |
Φόρτωση σελίδας Αξιολόγησης | ναι |
calculate_profile(answers) |
Υποβολή Αξιολόγησης | ναι |
create_session_prompt(...) |
Κάθε γύρος συνομιλίας | ναι |
ai_complete(messages, model, api_key, max_tokens) |
Τυπική κλήση ΤΝ | ναι (δρομολόγηση παρόχου κατά πρόθεμα μοντέλου) |
ai_complete_async(...) |
Παράλληλη αξιολόγηση ορίου κύκλου (v1.5.0) | ναι |
ai_complete_stream(...) |
Streaming απάντηση συνεδρίας (v1.6.0) | ναι |
recommend_method_switch(...) |
Ταμπλό + Συνεδρία | ναι |
on_session_complete(session, rating) |
Τέλος συνεδρίας | broadcast |
get_progress_summary(project_id) |
Widgets Ταμπλό | broadcast |
get_tool_recommendations(profile, lang) |
Εργαλεία Ταμπλό | broadcast |
Ροή δεδομένων¶
UI (React) → IStorageService
→ (API mode) FastAPI router → service → SQLAlchemy → SQLite
→ (Dexie mode) Dexie table → IndexedDB
↓
AI orchestrator → resolve_api_key (env > yaml > DB)
→ pluginforge → provider plugin's ai_complete*
→ Anthropic / OpenAI / Gemini SDK
Μονοκατευθυντική. Χωρίς άμεση πρόσβαση DB από routers (οι services κατέχουν τη δουλειά με SQLAlchemy). Χωρίς frontend κώδικα στο backend.
Διαχείριση σφαλμάτων¶
Frontend ApiError (status + detail) → toast για τον χρήστη
API client HTTP error → μετατροπή σε ApiError
Router Λεπτό, δεν πιάνει τίποτα. Παγκόσμιος handler αντιστοιχίζει.
Service Εκπέμπει υποκλάσεις AdaptiveLearnerError
Plugin Εκπέμπει PluginError(plugin_name, message)
External ExternalServiceError(service, message) για provider SDKs
Οι services ΠΟΤΕ δεν εκπέμπουν HTTPException· τα routers δεν
πιάνουν ΤΙΠΟΤΑ. Ο παγκόσμιος exception handler στο main.py
αντιστοιχίζει domain errors σε HTTP status codes. Δες
.claude/rules/code-hygiene.md για το πλήρες πρότυπο.
Επιμονή¶
- Backend: SQLAlchemy + SQLite. Μεταναστεύσεις Alembic στο
backend/migrations/versions/. - Επιφάνεια συγχρονισμού: 28 πίνακες (baseline v1.19.0). Γραμμές ιστορικού μόνο-προσάρτησης (sessions, messages, ratings, progress commits, step evaluations, method switches, imported conversations, imported messages, anki cards, study questions) συν μεταβλητές γραμμές settings + curriculum.
- Μορφή αντιγράφου: JSON· τα API keys εξαιρούνται κατά την εξαγωγή· η επαναφορά είναι συγχώνευση.
- Απομόνωση τεστ: τα directories παραγωγικών δεδομένων φέρουν ένα
marker
.adaptive-learner-production· αν ένα τεστ το δει ποτέ, η εκτέλεση τερματίζει μεpytest.exit(returncode=2).
Θεματοποίηση¶
5 θέματα (Classic, Cool Modern, Nord, Notebook, Studio) ×
light/dark = 10 παραλλαγές. CSS variables παντού· χωρίς Tailwind.
Custom properties στο frontend/src/styles/global.css. Τα νέα UI
στοιχεία ΠΡΕΠΕΙ να χρησιμοποιούν το σύνολο μεταβλητών.
Κινητά / PWA¶
Το @media (max-width: 768px) είναι η κανονική διακοπή για κινητά
(drawer hamburger, touch targets 44×44, stacked layouts).
Το @media (max-width: 360px) είναι το δίχτυ ασφαλείας για
πολύ στενές οθόνες. Οι στυλ desktop ≥769px παραμένουν αμετάβλητοι.
Service worker (Workbox μέσω vite-plugin-pwa): NetworkFirst
σε GET /api/ με timeout 4s, LRU 24h, όριο 60 εγγραφών.
Το μεταβλητό /api/ είναι NetworkOnly.