313 lines
9.9 KiB
Python
313 lines
9.9 KiB
Python
from pydantic import BaseModel, Field
|
||
from typing import Literal
|
||
|
||
FormatType = Literal[
|
||
"Guide",
|
||
"FullGuide",
|
||
"Rest",
|
||
]
|
||
|
||
ProviderType = Literal["claude", "minimax", "lokal"]
|
||
|
||
SourceType = Literal["thema", "projekt", "uni", "link"]
|
||
|
||
|
||
class GuideCreateRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
format: FormatType
|
||
instructions: str = Field(default="", max_length=2000)
|
||
provider: ProviderType = "claude"
|
||
ab_step: int | None = Field(default=None, ge=0, le=4) # re-run from guide step (0 outline … 4 read-exam); None = full/resume
|
||
|
||
|
||
class TopicCreateRequest(BaseModel):
|
||
name: str = Field(min_length=1, max_length=100)
|
||
|
||
|
||
class BlocksCreateRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
instructions: str = Field(default="", max_length=2000)
|
||
provider: ProviderType = "claude"
|
||
source_type: SourceType = "thema"
|
||
source_location: str = Field(default="", max_length=2000)
|
||
ab_phase: int | None = Field(default=None, ge=1, le=9) # re-run from a coarse phase (position in _phasen(topic), 1-based; up to 9: …outline/questions/artifacts); None = resume/continue without deleting
|
||
ab_step: int | None = Field(default=None, ge=0) # re-run from a fine sub-step (0-based index into _blocks_steps); takes precedence over ab_phase
|
||
to_step: int | None = Field(default=None, ge=0) # stop AFTER this fine sub-step (0-based index into _blocks_steps); None = run to the end
|
||
research: bool = True # False = "Continue": process the existing queue, search no new titles
|
||
|
||
|
||
class BlocksResetStepRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
ab_step: int = Field(ge=0) # ONLY reset from this sub-step (no regeneration)
|
||
|
||
|
||
class BlocksStep(BaseModel):
|
||
label: str
|
||
state: Literal["done", "active", "pending"]
|
||
|
||
|
||
class BlocksFineStep(BaseModel):
|
||
label: str
|
||
phase: str = ""
|
||
state: Literal["done", "active", "pending"]
|
||
|
||
|
||
class BlocksStatusResponse(BaseModel):
|
||
ready: bool
|
||
generating: bool
|
||
progress: str | None = None
|
||
error: str | None = None
|
||
partial: bool = False
|
||
steps: list[BlocksStep] = []
|
||
feine_steps: list[BlocksFineStep] = []
|
||
|
||
|
||
class ProjectResponse(BaseModel):
|
||
name: str
|
||
|
||
|
||
class FolderResponse(BaseModel):
|
||
name: str
|
||
location: str # path relative to the repo root (e.g. "projects/foo")
|
||
|
||
|
||
class BlocksSourceUpdate(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
type: SourceType = "thema"
|
||
location: str = Field(default="", max_length=2000)
|
||
spec: str = Field(default="", max_length=2000)
|
||
|
||
|
||
class BlocksSourceResponse(BaseModel):
|
||
type: SourceType
|
||
location: str
|
||
spec: str
|
||
|
||
|
||
class SubblockInfo(BaseModel):
|
||
title: str
|
||
level: Literal["beginner", "advanced", "expert", "easy", "medium", "hard"]
|
||
relevance: Literal["relevant", "peripheral"] | None = None
|
||
|
||
|
||
class BlockOverview(BaseModel):
|
||
num: int
|
||
title: str
|
||
description: str = ""
|
||
subblocks: list[SubblockInfo] = []
|
||
|
||
|
||
class ProviderInfo(BaseModel):
|
||
id: str
|
||
available: bool
|
||
|
||
|
||
class GuideResponse(BaseModel):
|
||
id: str
|
||
topic: str
|
||
format: str
|
||
status: str
|
||
progress: str | None = None
|
||
step: int | None = None
|
||
error_msg: str | None = None
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
class ChatMessage(BaseModel):
|
||
role: Literal["user", "assistant"]
|
||
content: str = Field(min_length=1, max_length=8000)
|
||
|
||
|
||
class GuideChatRequest(BaseModel):
|
||
section: str = Field(default="", max_length=20000)
|
||
outline: str = Field(default="", max_length=8000)
|
||
messages: list[ChatMessage] = Field(min_length=1)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class GuideChatResponse(BaseModel):
|
||
reply: str
|
||
|
||
|
||
class ElementResponse(BaseModel):
|
||
id: str
|
||
topic: str
|
||
title: str
|
||
description: str = ""
|
||
examples: list[str] = []
|
||
hints: list[str] = []
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
class ElementCreateRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
hint: str = Field(default="", max_length=500)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class ElementUpdateRequest(BaseModel):
|
||
title: str | None = Field(default=None, max_length=200)
|
||
description: str | None = None
|
||
examples: list[str] | None = None
|
||
hints: list[str] | None = None
|
||
|
||
|
||
class ElementCheckRequest(BaseModel):
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class ElementSuggestion(BaseModel):
|
||
text: str
|
||
target: Literal["description", "examples", "hints"]
|
||
content: str
|
||
|
||
|
||
class ElementCheckResponse(BaseModel):
|
||
suggestions: list[ElementSuggestion]
|
||
|
||
|
||
class ElementStyleChange(BaseModel):
|
||
text: str
|
||
action: Literal["remove", "adjust", "add"]
|
||
target: Literal["title", "description", "examples", "hints"]
|
||
index: int | None = None
|
||
content: str = ""
|
||
|
||
|
||
class ElementStyleResponse(BaseModel):
|
||
changes: list[ElementStyleChange]
|
||
|
||
|
||
class ElementChatRequest(BaseModel):
|
||
messages: list[ChatMessage] = Field(min_length=1)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class ElementChatResponse(BaseModel):
|
||
reply: str
|
||
changes: list[ElementStyleChange] = []
|
||
|
||
|
||
class ElementRefineRequest(BaseModel):
|
||
suggestion: ElementStyleChange
|
||
instruction: str = Field(min_length=1, max_length=2000)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class ElementRefineResponse(BaseModel):
|
||
change: ElementStyleChange
|
||
|
||
|
||
class ProgressUpdate(BaseModel):
|
||
chapter: str = Field(min_length=1, max_length=100)
|
||
done: bool
|
||
|
||
|
||
class ProgressResponse(BaseModel):
|
||
chapters: list[str]
|
||
|
||
|
||
# --- Block learning ---
|
||
|
||
class BlockChatRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
block: str = Field(min_length=1, max_length=200)
|
||
section: str = Field(default="", max_length=20000) # detailed version
|
||
section_compact: str = Field(default="", max_length=20000) # compact version (mnemonics)
|
||
messages: list[ChatMessage] = Field(min_length=1)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class BlockChatResponse(BaseModel):
|
||
reply: str
|
||
|
||
|
||
class BlockExamRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
block: str = Field(min_length=1, max_length=200)
|
||
section: str = Field(default="", max_length=20000) # detailed version
|
||
section_compact: str = Field(default="", max_length=20000) # compact version (mnemonics)
|
||
action: Literal[
|
||
"question", "discussion", "answer", "answer_check",
|
||
"quiz_question", "quiz_answer", "gap_question", "gap_answer",
|
||
] = "question"
|
||
question: str = Field(default="", max_length=2000) # currently checked question (for discussion/answer); base anchor
|
||
selection: list[int] = [] # quiz/gap-text choice: option indices picked by the learner
|
||
correct: list[int] = [] # quiz/gap-text choice: correct indices (the client keeps them from generation)
|
||
solution: str = Field(default="", max_length=500) # gap text free: expected term
|
||
alternatives: list[str] = [] # gap text free: accepted synonyms
|
||
input: str = Field(default="", max_length=500) # gap text free: typed term
|
||
schwer: bool = False # variant: easy (+1/−1) vs hard (+3/−1)
|
||
last_rating: str = Field(default="", max_length=2000) # feedback of the last rating (context for discussion)
|
||
avoid: list[str] = [] # already-asked + earmarked questions — don't repeat them in substance
|
||
asked_again: bool = False # asked_again was used for this question → gain capped at +1
|
||
reason: str = Field(default="", max_length=2000) # "thorough check": why dissatisfied with the rating
|
||
pattern: str = Field(default="", max_length=2000) # drawn question pattern (seed); empty → live generation (fallback)
|
||
# Base + cap are kept server-side (anchor / subs×25) — the client cap is only a hint.
|
||
cap: int = Field(default=10, ge=1, le=10000) # score cap = unlocked subblocks × 25
|
||
messages: list[ChatMessage] = [] # dialog so far; empty = first question
|
||
provider: ProviderType = "claude"
|
||
thorough: bool = False # "thorough check": rating with a strong model (role guide)
|
||
|
||
|
||
class QuizOption(BaseModel):
|
||
text: str
|
||
correct: bool
|
||
|
||
|
||
class BlockExamResponse(BaseModel):
|
||
question: str | None = None
|
||
reply: str | None = None
|
||
feedback: str | None = None
|
||
points: int | None = None # points delta of this answer (−2 … +3); fast = expected
|
||
rating: Literal["gut", "neutral", "schlecht"] | None = None # from the sign, for coloring
|
||
options: list[QuizOption] | None = None # quiz: 4 options + correct flags
|
||
sentence: str | None = None # gap text: sentence with a gap (___)
|
||
solution: str | None = None # gap text: expected term
|
||
alternatives: list[str] | None = None # gap text: accepted synonyms
|
||
good_answers: int
|
||
streak: int = 0 # current run of correct answers (per block)
|
||
cap: int = 10 # cap_final = all subs × 25 — the frontend derives the learning level
|
||
|
||
|
||
class BlockLearnState(BaseModel):
|
||
good_answers: int
|
||
streak: int = 0
|
||
cap: int = 0 # cap_final = all subblocks × 25
|
||
cap_aktuell: int = 0 # reachable cap of the currently unlocked level
|
||
freie_level: int = 1 # 1=A · 2=F · 3=E · 4=V
|
||
|
||
|
||
class BlockLearnStateResponse(BaseModel):
|
||
blocks: dict[str, BlockLearnState]
|
||
|
||
|
||
# --- Block content: check + apply one section on demand (focus, right-click) ---
|
||
|
||
class BlockPruefenRequest(BaseModel):
|
||
block: str = Field(min_length=1, max_length=200)
|
||
spot: str = "ausführlich" # "compact" | "ausführlich" (displayed field)
|
||
snippet: str = Field(min_length=1, max_length=20000) # raw markdown block
|
||
hint: str = Field(default="", max_length=2000) # optional addition (✏️)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class BlockPruefenResponse(BaseModel):
|
||
revised: str # corrected block as markdown
|
||
|
||
|
||
class BlockUebernehmenRequest(BaseModel):
|
||
block: str = Field(min_length=1, max_length=200)
|
||
spot: str = "ausführlich"
|
||
alt: str = Field(min_length=1, max_length=20000)
|
||
revised: str = Field(default="", max_length=20000)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class BlockUebernehmenResponse(BaseModel):
|
||
compact: str
|
||
md: str
|
||
found: bool
|