Files
creator/backend/models.py
2026-07-04 02:32:31 +02:00

268 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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=5) # re-run from board stage (0 lernziele … 5 lesbarkeit); None = full/resume
class GuideBoardResetRequest(BaseModel):
topic: str = Field(min_length=1, max_length=100)
format: FormatType = "Guide"
ab_stage: int = Field(ge=0, le=5) # reset cards back to this board stage (no generation)
class TopicCreateRequest(BaseModel):
name: str = Field(min_length=1, max_length=100)
class QaRunRequest(BaseModel):
topic: str = Field(min_length=1, max_length=100)
llm: bool = True # wie das Gate: Echtheits-/Dubletten-Stichprobe inklusive
class RepairRequest(BaseModel):
topic: 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)
research: bool = True # False = Continue: drain the existing kanban queue, no new search
qa_force: bool = False # True = übersteuert ein pausierendes QA-Gate („Trotzdem fortsetzen")
class BlocksCardRestartRequest(BaseModel):
topic: str = Field(min_length=1)
card_id: str = Field(min_length=1, max_length=200)
class GuideFormatRequest(BaseModel):
topic: str = Field(min_length=1)
format: str = Field(min_length=1)
class PracticeAnswerRequest(BaseModel):
topic: str = Field(min_length=1)
block_norm: str = Field(min_length=1, max_length=300)
sub_norm: str = Field(max_length=300)
correct: bool
class GuideCardResetRequest(BaseModel):
topic: str = Field(min_length=1)
format: str = Field(min_length=1)
block_norm: str = Field(min_length=1, max_length=200)
ab_stage: int = Field(ge=0, le=5)
class BlocksResetStageRequest(BaseModel):
topic: str = Field(min_length=1, max_length=100)
board: Literal["inventory", "artefacts"]
stage: str = Field(min_length=1, max_length=40) # kanban column to reset back to
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 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
# --- 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