271 lines
8.2 KiB
Python
271 lines
8.2 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=5) # Re-Run ab Guide-Schritt (0 Auswahl … 5 Lese-Prüfung); None = voll/Resume
|
||
|
||
|
||
class TopicCreateRequest(BaseModel):
|
||
name: str = Field(min_length=1, max_length=100)
|
||
|
||
|
||
class BausteineCreateRequest(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_ort: str = Field(default="", max_length=2000)
|
||
ab_phase: int | None = Field(default=None, ge=1, le=5) # Re-Run ab grober Phase (1 Inventar … 5 Fragen); None = Resume/Fortsetzen ohne Löschen
|
||
|
||
|
||
class BausteineStep(BaseModel):
|
||
label: str
|
||
state: Literal["done", "active", "pending"]
|
||
|
||
|
||
class BausteineStatusResponse(BaseModel):
|
||
ready: bool
|
||
generating: bool
|
||
progress: str | None = None
|
||
error: str | None = None
|
||
partial: bool = False
|
||
steps: list[BausteineStep] = []
|
||
|
||
|
||
class ProjectResponse(BaseModel):
|
||
name: str
|
||
|
||
|
||
class FolderResponse(BaseModel):
|
||
name: str
|
||
ort: str # relativer Pfad ab Repo-Root (z.B. "projects/foo")
|
||
|
||
|
||
class BausteineQuelleUpdate(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
type: SourceType = "thema"
|
||
ort: str = Field(default="", max_length=2000)
|
||
spec: str = Field(default="", max_length=2000)
|
||
|
||
|
||
class BausteineQuelleResponse(BaseModel):
|
||
type: SourceType
|
||
ort: str
|
||
spec: str
|
||
|
||
|
||
class SubbausteinInfo(BaseModel):
|
||
titel: str
|
||
stufe: Literal["einfach", "mittel", "schwer"]
|
||
relevanz: Literal["relevant", "rand"] | None = None
|
||
|
||
|
||
class BausteinUebersicht(BaseModel):
|
||
num: int
|
||
titel: str
|
||
beschreibung: str = ""
|
||
subbausteine: list[SubbausteinInfo] = []
|
||
|
||
|
||
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["entfernen", "anpassen", "hinzufuegen"]
|
||
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]
|
||
|
||
|
||
# --- Baustein-Lernen ---
|
||
|
||
class BausteinChatRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
baustein: str = Field(min_length=1, max_length=200)
|
||
section: str = Field(default="", max_length=20000) # ausführliche Fassung
|
||
section_kompakt: str = Field(default="", max_length=20000) # kompakte Fassung (Merksätze)
|
||
messages: list[ChatMessage] = Field(min_length=1)
|
||
provider: ProviderType = "claude"
|
||
|
||
|
||
class BausteinChatResponse(BaseModel):
|
||
reply: str
|
||
|
||
|
||
class BausteinPruefungRequest(BaseModel):
|
||
topic: str = Field(min_length=1, max_length=100)
|
||
baustein: str = Field(min_length=1, max_length=200)
|
||
section: str = Field(default="", max_length=20000) # ausführliche Fassung
|
||
section_kompakt: str = Field(default="", max_length=20000) # kompakte Fassung (Merksätze)
|
||
aktion: Literal[
|
||
"frage", "diskussion", "antwort", "antwort_pruefen",
|
||
"quiz_frage", "quiz_antwort", "lueck_frage", "lueck_antwort",
|
||
] = "frage"
|
||
frage: str = Field(default="", max_length=2000) # aktuell geprüfte Frage (für diskussion/antwort); Anker der Basis
|
||
auswahl: list[int] = [] # Quiz/Lückentext-Auswahl: vom Lerner gewählte Options-Indizes
|
||
korrekt: list[int] = [] # Quiz/Lückentext-Auswahl: korrekte Indizes (Client hält sie aus der Generierung)
|
||
loesung: str = Field(default="", max_length=500) # Lückentext frei: erwarteter Begriff
|
||
alternativen: list[str] = [] # Lückentext frei: akzeptierte Synonyme
|
||
eingabe: str = Field(default="", max_length=500) # Lückentext frei: getippter Begriff
|
||
schwer: bool = False # Variante: leicht (+1/−1) vs schwer (+3/−1)
|
||
letzte_bewertung: str = Field(default="", max_length=2000) # Feedback der letzten Bewertung (Kontext für diskussion)
|
||
vermeide: list[str] = [] # schon gestellte + vorgemerkte Fragen — sinngemäß nicht wiederholen
|
||
nachgefragt: bool = False # für diese Frage wurde nachgefragt → Gewinn auf +1 gedeckelt
|
||
begruendung: str = Field(default="", max_length=2000) # „Gründlich prüfen": warum mit der Bewertung unzufrieden
|
||
muster: str = Field(default="", max_length=2000) # gezogenes Frage-Muster (Saat); leer → Live-Generierung (Fallback)
|
||
# Basis wird serverseitig je Frage geführt (offene_frage-Anker) — nicht mehr vom Client.
|
||
cap: int = Field(default=6, ge=6, le=30) # Score-Deckel = Max des höchsten erstellten Formats (6/12/18/30)
|
||
messages: list[ChatMessage] = [] # Dialog bisher; leer = erste Frage
|
||
provider: ProviderType = "claude"
|
||
gruendlich: bool = False # „Gründlich prüfen": Bewertung mit starkem Modell (role guide)
|
||
|
||
|
||
class QuizOption(BaseModel):
|
||
text: str
|
||
korrekt: bool
|
||
|
||
|
||
class BausteinPruefungResponse(BaseModel):
|
||
frage: str | None = None
|
||
reply: str | None = None
|
||
feedback: str | None = None
|
||
punkte: int | None = None # Punkt-Delta dieser Antwort (−2 … +3); schnell = voraussichtlich
|
||
bewertung: Literal["gut", "neutral", "schlecht"] | None = None # aus Vorzeichen, fürs Einfärben
|
||
optionen: list[QuizOption] | None = None # Quiz: 4 Optionen + Korrekt-Flags
|
||
satz: str | None = None # Lückentext: Satz mit Lücke (___)
|
||
loesung: str | None = None # Lückentext: erwarteter Begriff
|
||
alternativen: list[str] | None = None # Lückentext: akzeptierte Synonyme
|
||
gute_antworten: int
|
||
absolviert: bool
|
||
verstanden: bool = False
|
||
gemeistert: bool = False
|
||
|
||
|
||
class BausteinLernstand(BaseModel):
|
||
gute_antworten: int
|
||
streak: int = 0
|
||
absolviert: bool
|
||
verstanden: bool
|
||
gemeistert: bool
|
||
|
||
|
||
class BausteinLernstandResponse(BaseModel):
|
||
bausteine: dict[str, BausteinLernstand]
|