第一次提交

This commit is contained in:
2026-08-01 16:50:55 +08:00
commit c8f9e39039
68 changed files with 6016 additions and 0 deletions
View File
+47
View File
@@ -0,0 +1,47 @@
"""作答记录与 AI 功能的 schema。"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class AttemptCreate(BaseModel):
user_answer: list[str] = Field(default_factory=list)
class AttemptOut(BaseModel):
"""一次作答记录。is_correct 为 null 表示"已记录,尚未判分""""
model_config = ConfigDict(from_attributes=True)
id: str
question_id: str
user_answer: list[str] | None
is_correct: bool | None
judged_by: str
ai_feedback_markdown: str | None
created_at: datetime
class SelfJudge(BaseModel):
"""自评:用户自己判定对错,不花 AI 额度。"""
is_correct: bool
class JudgeResult(BaseModel):
"""按需判分的结果。"""
attempt_id: str
is_correct: bool | None
judged_by: str
feedback_markdown: str | None = None
key_points_missed: list[str] = Field(default_factory=list)
correct_answer: list[str] | None = None
class ExplainOut(BaseModel):
explanation_markdown: str
class TagsOut(BaseModel):
tags: list[str]
+22
View File
@@ -0,0 +1,22 @@
"""认证相关的请求/响应 schema。"""
from pydantic import BaseModel, ConfigDict, Field
class RegisterRequest(BaseModel):
username: str = Field(min_length=3, max_length=64)
password: str = Field(min_length=6, max_length=128)
email: str | None = Field(default=None, max_length=255)
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
class UserOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
username: str
email: str | None
is_active: bool
+30
View File
@@ -0,0 +1,30 @@
"""图片与任务的 schema。"""
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.models.job import JobStatus
class ImageOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
object_key: str
mime: str
size_bytes: int
class JobOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
status: JobStatus
image_id: str | None
# 一张图可能拆成多条笔记。任务未完成时库里是 NULL,对前端统一成空数组
result_question_ids: list[str] = Field(default_factory=list)
engine_used: str | None
error_message: str | None
@field_validator("result_question_ids", mode="before")
@classmethod
def _none_to_empty(cls, v):
return v or []
+29
View File
@@ -0,0 +1,29 @@
"""首页概览与复习清单的 schema。"""
from pydantic import BaseModel
from app.schemas.question import QuestionOut
class TagStat(BaseModel):
name: str
count: int
needs_review_count: int
class NotebookOverview(BaseModel):
"""首页一次拿全:知识点导航 + 待复习计数 + 最近记录。"""
total_notes: int
review_count: int
untagged_count: int
tags: list[TagStat]
recent: list[QuestionOut]
class ReviewItem(BaseModel):
question: QuestionOut
# 为什么在复习清单里:手动标记 / 最近一次答错
marked: bool
last_wrong: bool
wrong_count: int
last_wrong_at: str | None
+86
View File
@@ -0,0 +1,86 @@
"""笔记相关的请求/响应 schema。"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from app.models.question import QuestionType
class OptionIn(BaseModel):
key: str = Field(max_length=8) # A/B/C/D 等
text_markdown: str
class QuestionBase(BaseModel):
# 默认未分类:拍照记下来就能存,不强制先想清楚题型
type: QuestionType = QuestionType.unclassified
stem_markdown: str
options: list[OptionIn] | None = None
correct_answer: list[str] | None = None
explanation_markdown: str | None = None
my_note_markdown: str | None = None
needs_review: bool = False
difficulty: int | None = Field(default=None, ge=1, le=5)
class QuestionCreate(QuestionBase):
tags: list[str] = Field(default_factory=list)
class QuestionUpdate(BaseModel):
"""全部可选,只更新提供的字段。"""
type: QuestionType | None = None
stem_markdown: str | None = None
options: list[OptionIn] | None = None
correct_answer: list[str] | None = None
explanation_markdown: str | None = None
my_note_markdown: str | None = None
needs_review: bool | None = None
difficulty: int | None = Field(default=None, ge=1, le=5)
tags: list[str] | None = None
class QuestionOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
type: QuestionType
stem_markdown: str
options: list[dict] | None
correct_answer: list[str] | None
explanation_markdown: str | None
my_note_markdown: str | None
needs_review: bool
difficulty: int | None
source_image_id: str | None
ocr_engine: str | None
tags: list[str] = Field(default_factory=list)
created_at: datetime | None = None
updated_at: datetime | None = None
@classmethod
def from_model(cls, q) -> "QuestionOut":
return cls(
id=q.id,
type=q.type,
stem_markdown=q.stem_markdown,
options=q.options,
correct_answer=q.correct_answer,
explanation_markdown=q.explanation_markdown,
my_note_markdown=q.my_note_markdown,
needs_review=q.needs_review,
difficulty=q.difficulty,
source_image_id=q.source_image_id,
ocr_engine=q.ocr_engine,
tags=[t.name for t in q.tags],
created_at=q.created_at,
updated_at=q.updated_at,
)
class QuestionList(BaseModel):
items: list[QuestionOut]
total: int
page: int
page_size: int
+17
View File
@@ -0,0 +1,17 @@
"""用户 AI 配置 schema。api_key 只写不回显明文。"""
from pydantic import BaseModel
class SettingsOut(BaseModel):
llm_base_url: str | None = None
llm_text_model: str | None = None
llm_vision_model: str | None = None
has_api_key: bool = False # 是否已配置 key(不回显明文)
class SettingsUpdate(BaseModel):
llm_base_url: str | None = None
llm_text_model: str | None = None
llm_vision_model: str | None = None
# 留空表示不修改现有 key;显式传值则更新
api_key: str | None = None