Add spell practice, memory analytics, auth persistence, and word library UX.

Includes per-word training stats and curves, quiz session auto-save, remember-login,
paginated word list with floating page arrows, and Obsidian-style relationship graph baseline.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-04 14:57:39 -07:00
parent bd7635986a
commit 68c5efe573
30 changed files with 2560 additions and 63 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ def login(data: UserLogin, db: Session = Depends(get_db)):
if not user or not verify_password(data.password, user.password_hash):
raise HTTPException(status_code=401, detail="用户名或密码错误")
token = create_access_token(user.id, user.username)
token = create_access_token(user.id, user.username, remember=data.remember)
return TokenResponse(access_token=token)
+10
View File
@@ -24,6 +24,15 @@ def daily_quiz(
return DailyQuizResponse(**result)
@router.get("/spell", response_model=DailyQuizResponse)
def spell_quiz(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
result = quiz_service.get_spell_quiz(db, current_user)
return DailyQuizResponse(**result)
@router.post("/answer", response_model=QuizAnswerResponse)
def submit_answer(
data: QuizAnswerRequest,
@@ -37,6 +46,7 @@ def submit_answer(
data.question_type,
data.user_answer,
data.correct_answer,
data.duration_seconds or 0,
)
return QuizAnswerResponse(**result)
+25 -1
View File
@@ -6,7 +6,14 @@ from sqlalchemy.orm import Session
from auth import get_current_user
from database import get_db
from models import User
from schemas import WordCreate, WordOut, WordUpdate
from schemas import (
MemoryVisualizationResponse,
WordCreate,
WordMemoryDetailResponse,
WordOut,
WordUpdate,
)
from services.memory_visual_service import memory_visual_service
from services.word_service import word_service
router = APIRouter(prefix="/api/words", tags=["words"])
@@ -31,6 +38,23 @@ def list_words(
return word_service.list_words(db, current_user, status)
@router.get("/memory-viz", response_model=MemoryVisualizationResponse)
def memory_visualization(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
return memory_visual_service.get_visualization(db, current_user)
@router.get("/{word_id}/memory", response_model=WordMemoryDetailResponse)
def word_memory(
word_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
return memory_visual_service.get_word_memory(db, current_user, word_id)
@router.get("/{word_id}", response_model=WordOut)
def get_word(
word_id: int,