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:
+95
-2
@@ -1,5 +1,5 @@
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
|
||||
|
||||
# Auth
|
||||
@@ -11,6 +11,7 @@ class UserRegister(BaseModel):
|
||||
class UserLogin(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
remember: bool = True
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
@@ -72,14 +73,104 @@ class WordOut(BaseModel):
|
||||
wrong_count: int
|
||||
consecutive_correct_count: int
|
||||
mastery_score: int
|
||||
train_count: int = 0
|
||||
total_train_seconds: int = 0
|
||||
review_due_date: Optional[str] = None
|
||||
last_reviewed_at: Optional[str] = None
|
||||
created_at: str
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
@property
|
||||
def entered_at(self) -> str:
|
||||
"""词库进入时间(与 created_at 一致)。"""
|
||||
return self.created_at
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class MemoryCurvePoint(BaseModel):
|
||||
day_index: int
|
||||
date: str
|
||||
forgetting: float
|
||||
mastery: float
|
||||
risk: float
|
||||
|
||||
|
||||
class MemoryFutureRiskPoint(BaseModel):
|
||||
day_offset: int
|
||||
date: str
|
||||
risk: float
|
||||
|
||||
|
||||
class MemoryGraphNode(BaseModel):
|
||||
id: str
|
||||
label: str
|
||||
zh: str
|
||||
status: str
|
||||
mastery: int
|
||||
entered_at: str
|
||||
size: int
|
||||
|
||||
|
||||
class MemoryGraphLink(BaseModel):
|
||||
source: str
|
||||
target: str
|
||||
kind: str
|
||||
strength: float
|
||||
|
||||
|
||||
class MemoryGraph(BaseModel):
|
||||
nodes: list[MemoryGraphNode]
|
||||
links: list[MemoryGraphLink]
|
||||
|
||||
|
||||
class MemoryWordSummary(BaseModel):
|
||||
id: int
|
||||
en: str
|
||||
zh: str
|
||||
status: str
|
||||
mastery_score: int
|
||||
correct_count: int = 0
|
||||
wrong_count: int = 0
|
||||
train_count: int = 0
|
||||
total_train_seconds: int = 0
|
||||
entered_at: str
|
||||
retention_now: float
|
||||
risk_7d: float
|
||||
|
||||
|
||||
class WordMemoryCurvePoint(BaseModel):
|
||||
date: str
|
||||
datetime: str
|
||||
forgetting: float
|
||||
mastery: float
|
||||
risk: float
|
||||
wrong_count: int
|
||||
train_count: int
|
||||
train_seconds: int
|
||||
is_correct: Optional[bool] = None
|
||||
|
||||
|
||||
class WordMemoryDetailResponse(BaseModel):
|
||||
word_id: int
|
||||
en: str
|
||||
zh: str
|
||||
correct_count: int
|
||||
wrong_count: int
|
||||
train_count: int
|
||||
total_train_seconds: int
|
||||
curve_points: list[WordMemoryCurvePoint]
|
||||
future_risk: list[MemoryFutureRiskPoint]
|
||||
|
||||
|
||||
class MemoryVisualizationResponse(BaseModel):
|
||||
curve_points: list[MemoryCurvePoint]
|
||||
future_risk: list[MemoryFutureRiskPoint]
|
||||
words: list[MemoryWordSummary]
|
||||
graph: MemoryGraph
|
||||
|
||||
|
||||
# Quiz
|
||||
class QuizOption(BaseModel):
|
||||
label: str
|
||||
@@ -90,8 +181,9 @@ class QuizQuestion(BaseModel):
|
||||
word_id: int
|
||||
question_type: str
|
||||
prompt: str
|
||||
options: list[QuizOption]
|
||||
options: list[QuizOption] = []
|
||||
correct_answer: str
|
||||
phonetic: Optional[str] = None
|
||||
|
||||
|
||||
class DailyQuizResponse(BaseModel):
|
||||
@@ -104,6 +196,7 @@ class QuizAnswerRequest(BaseModel):
|
||||
question_type: str
|
||||
user_answer: str
|
||||
correct_answer: str
|
||||
duration_seconds: Optional[int] = Field(None, ge=0, le=3600)
|
||||
|
||||
|
||||
class QuizAnswerResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user