Elemente: Aufgabe/Lösung-Felder entfernt

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
team3
2026-06-12 07:44:39 +02:00
parent 54eaa1c89b
commit 693475128c
9 changed files with 23 additions and 72 deletions

View File

@@ -42,8 +42,6 @@ CREATE TABLE IF NOT EXISTS elements (
description TEXT NOT NULL DEFAULT '',
examples TEXT NOT NULL DEFAULT '[]',
hints TEXT NOT NULL DEFAULT '[]',
aufgabe TEXT NOT NULL DEFAULT '',
loesung TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
@@ -70,11 +68,6 @@ async def init_db():
await db.execute("ALTER TABLE guides ADD COLUMN step INTEGER")
except aiosqlite.OperationalError:
pass
for col in ("aufgabe", "loesung"): # Migration für Elemente ohne Aufgabe/Lösung
try:
await db.execute(f"ALTER TABLE elements ADD COLUMN {col} TEXT NOT NULL DEFAULT ''")
except aiosqlite.OperationalError:
pass
await db.execute(
"UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server-Neustart' "
"WHERE status IN ('queued', 'generating')"
@@ -173,8 +166,8 @@ def _element_row(row, cursor) -> dict:
async def create_element(element: dict) -> dict:
db = await get_db()
await db.execute(
"""INSERT INTO elements (id, topic, title, description, examples, hints, aufgabe, loesung, created_at, updated_at)
VALUES (:id, :topic, :title, :description, :examples, :hints, :aufgabe, :loesung, :created_at, :updated_at)""",
"""INSERT INTO elements (id, topic, title, description, examples, hints, created_at, updated_at)
VALUES (:id, :topic, :title, :description, :examples, :hints, :created_at, :updated_at)""",
{**element, "examples": json.dumps(element["examples"], ensure_ascii=False),
"hints": json.dumps(element["hints"], ensure_ascii=False)},
)

View File

@@ -1396,8 +1396,6 @@ def _element_fields(data: dict) -> dict | None:
"description": str(data.get("description", "")).strip(),
"examples": listen["examples"],
"hints": listen["hints"],
"aufgabe": str(data.get("aufgabe", "")).strip(),
"loesung": str(data.get("loesung", "")).strip(),
}
@@ -1420,7 +1418,7 @@ def _topic_context(topic: str, limit: int = 12000) -> str:
async def generate_element(topic: str, hint: str, provider: str = DEFAULT_PROVIDER) -> dict:
"""Erstellt Element-Felder per KI. Fallback: nur Titel aus dem Stichwort."""
fallback = {"title": hint.strip() or "Neues Element", "description": "", "examples": [], "hints": [], "aufgabe": "", "loesung": ""}
fallback = {"title": hint.strip() or "Neues Element", "description": "", "examples": [], "hints": []}
try:
prompt = _prompt(
"Element-Create",
@@ -1456,7 +1454,7 @@ def _parse_suggestions(stdout: str) -> list[dict] | None:
text = str(s.get("text", "")).strip()
target = s.get("target")
content = str(s.get("content", "")).strip()
if text and content and target in ("description", "examples", "hints", "aufgabe", "loesung"):
if text and content and target in ("description", "examples", "hints"):
if target == "examples":
content = _fence(content)
suggestions.append({"text": text, "target": target, "content": content})
@@ -1467,7 +1465,7 @@ async def check_element(element: dict, provider: str = DEFAULT_PROVIDER) -> list
"""Zweischrittige Prüfung auf fehlende Infos: Recherche → Verifizieren. None bei Fehler."""
try:
element_json = json.dumps(
{k: element[k] for k in ("title", "description", "examples", "hints", "aufgabe", "loesung")},
{k: element[k] for k in ("title", "description", "examples", "hints")},
ensure_ascii=False, indent=1,
)
context = _topic_context(element["topic"])
@@ -1504,7 +1502,7 @@ async def check_element(element: dict, provider: str = DEFAULT_PROVIDER) -> list
def _element_json(element: dict) -> str:
return json.dumps(
{k: element[k] for k in ("title", "description", "examples", "hints", "aufgabe", "loesung")},
{k: element[k] for k in ("title", "description", "examples", "hints")},
ensure_ascii=False, indent=1,
)
@@ -1520,7 +1518,7 @@ def _validate_change(c, element: dict) -> dict | None:
content = str(c.get("content", "")).strip()
if not text or action not in ("entfernen", "anpassen", "hinzufuegen"):
return None
if target not in ("title", "description", "examples", "hints", "aufgabe", "loesung"):
if target not in ("title", "description", "examples", "hints"):
return None
if action in ("anpassen", "hinzufuegen") and not content:
return None

View File

@@ -86,8 +86,6 @@ class ElementResponse(BaseModel):
description: str = ""
examples: list[str] = []
hints: list[str] = []
aufgabe: str = ""
loesung: str = ""
created_at: str
updated_at: str
@@ -103,8 +101,6 @@ class ElementUpdateRequest(BaseModel):
description: str | None = None
examples: list[str] | None = None
hints: list[str] | None = None
aufgabe: str | None = None
loesung: str | None = None
class ElementCheckRequest(BaseModel):
@@ -113,7 +109,7 @@ class ElementCheckRequest(BaseModel):
class ElementSuggestion(BaseModel):
text: str
target: Literal["description", "examples", "hints", "aufgabe", "loesung"]
target: Literal["description", "examples", "hints"]
content: str
@@ -124,7 +120,7 @@ class ElementCheckResponse(BaseModel):
class ElementStyleChange(BaseModel):
text: str
action: Literal["entfernen", "anpassen", "hinzufuegen"]
target: Literal["title", "description", "examples", "hints", "aufgabe", "loesung"]
target: Literal["title", "description", "examples", "hints"]
index: int | None = None
content: str = ""

View File

@@ -18,7 +18,7 @@ const query = ref('')
const creating = ref(false)
const selected = ref(null)
const tab = ref('overview') // 'overview' | 'chat' | 'edit'
const edit = ref({ title: '', description: '', examples: [], hints: [], aufgabe: '', loesung: '' })
const edit = ref({ title: '', description: '', examples: [], hints: [] })
const savingEdit = ref(false)
watch(() => props.topic, load, { immediate: true })
@@ -93,8 +93,6 @@ function loadEdit() {
description: selected.value.description,
examples: [...selected.value.examples],
hints: [...selected.value.hints],
aufgabe: selected.value.aufgabe || '',
loesung: selected.value.loesung || '',
}
}
@@ -112,8 +110,6 @@ async function saveEdit() {
description: edit.value.description,
examples: edit.value.examples.filter((s) => s.trim()),
hints: edit.value.hints.filter((s) => s.trim()),
aufgabe: edit.value.aufgabe,
loesung: edit.value.loesung,
})
selected.value = updated
const idx = elements.value.findIndex((e) => e.id === updated.id)
@@ -250,19 +246,17 @@ async function applyStyleChange(i) {
const c = styleChanges.value[i]
applyingStyle.value = true
try {
const STRING_TARGETS = ['title', 'description', 'aufgabe', 'loesung']
const STRING_TARGETS = ['title', 'description']
const fields = {
title: selected.value.title,
description: selected.value.description,
examples: [...selected.value.examples],
hints: [...selected.value.hints],
aufgabe: selected.value.aufgabe || '',
loesung: selected.value.loesung || '',
}
if (c.action === 'entfernen') fields[c.target].splice(c.index, 1)
else if (c.action === 'hinzufuegen') {
if (c.target === 'title') fields.title = c.content
else if (c.target === 'description' || c.target === 'aufgabe' || c.target === 'loesung')
else if (c.target === 'description')
fields[c.target] = fields[c.target] ? fields[c.target] + '\n\n' + c.content : c.content
else fields[c.target].push(c.content)
} else if (STRING_TARGETS.includes(c.target)) fields[c.target] = c.content
@@ -448,25 +442,6 @@ async function send() {
/>
</div>
<div v-if="selected.aufgabe || styleAt('aufgabe').length || styleAdds('aufgabe').length" class="el-task-block">
<h4>Aufgabe</h4>
<div v-if="selected.aufgabe" class="el-entry markdown" v-html="renderMarkdown(selected.aufgabe)"></div>
<ElementSuggestion
v-for="[ci, c] in [...styleAt('aufgabe'), ...styleAdds('aufgabe')]"
:key="'sga' + ci" :change="c" :busy="suggBusy(ci)"
@apply="applyStyleChange(ci)" @dismiss="dismissStyleChange(ci)" @refine="(t) => refineChange(ci, t)"
/>
</div>
<div v-if="selected.loesung || styleAt('loesung').length || styleAdds('loesung').length" class="el-task-block">
<h4>Lösung</h4>
<div v-if="selected.loesung" class="el-entry markdown" v-html="renderMarkdown(selected.loesung)"></div>
<ElementSuggestion
v-for="[ci, c] in [...styleAt('loesung'), ...styleAdds('loesung')]"
:key="'sgl' + ci" :change="c" :busy="suggBusy(ci)"
@apply="applyStyleChange(ci)" @dismiss="dismissStyleChange(ci)" @refine="(t) => refineChange(ci, t)"
/>
</div>
<div v-if="checking || styling || statusMsg" class="el-check">
<p v-if="checking" class="check-empty busy-text">Prüft auf fehlende Infos</p>
<p v-if="styling" class="check-empty busy-text">Prüft den Stil</p>
@@ -522,12 +497,6 @@ async function send() {
<button class="edit-del" title="Entfernen" @click="edit.hints.splice(i, 1)">×</button>
</div>
<button class="edit-add" @click="edit.hints.push('')">+ Hinweis</button>
<label>Aufgabenstellung</label>
<textarea v-model="edit.aufgabe" placeholder="Aufgabenstellung"></textarea>
<label>Aufgabenlösung</label>
<textarea v-model="edit.loesung" placeholder="Aufgabenlösung"></textarea>
</div>
</template>
</aside>
@@ -786,13 +755,11 @@ async function send() {
color: var(--text);
}
.el-hints-block,
.el-task-block {
.el-hints-block {
margin-top: 0.9rem;
}
.el-hints-block h4,
.el-task-block h4 {
.el-hints-block h4 {
margin: 0 0 0.35rem;
font-size: 0.72rem;
font-weight: 700;

View File

@@ -17,8 +17,8 @@ Umfang: SO LANG WIE NÖTIG und SO KURZ WIE MÖGLICH. Markdown: `inline-code` fü
Jeder Vorschlag:
- text: kurz, was geändert wird (max. 12 Wörter, reiner Text)
- action: "entfernen" | "anpassen" | "hinzufuegen"
- target: "title" | "description" | "examples" | "hints" | "aufgabe" | "loesung"
- index: 0-basierte Position im AKTUELLEN examples- bzw. hints-Array (bei title/description/aufgabe/loesung und hinzufuegen: null)
- target: "title" | "description" | "examples" | "hints"
- index: 0-basierte Position im AKTUELLEN examples- bzw. hints-Array (bei title/description und hinzufuegen: null)
- content: der neue vollständige Inhalt (bei entfernen: leer)
"entfernen" nur für examples/hints. Nur Vorschläge machen, die die Nutzer-Anweisung verlangt.

View File

@@ -6,12 +6,12 @@ AKTUELLES ELEMENT (JSON):
KONTEXT (Auszüge aus dem Themen-Material):
{context}
RECHERCHE — sammle breit alle Kandidaten: fehlende Kernaussagen, wichtige Varianten, typische Stolperfallen, Best Practices. Fehlt eine Übungsaufgabe (aufgabe) oder deren Lösung (loesung), schlage sie vor. Lieber einen Kandidaten zu viel als einen zu wenig — die Bewertung passiert in einem zweiten Schritt. Nichts vorschlagen, was das Element schon enthält.
RECHERCHE — sammle breit alle Kandidaten: fehlende Kernaussagen, wichtige Varianten, typische Stolperfallen, Best Practices. Lieber einen Kandidaten zu viel als einen zu wenig — die Bewertung passiert in einem zweiten Schritt. Nichts vorschlagen, was das Element schon enthält.
Jeder Kandidat:
- text: kurze Beschreibung der Lücke (max. 12 Wörter, reiner Text)
- target: "description" | "examples" | "hints" | "aufgabe" | "loesung"
- content: fertiger Inhalt zum Einfügen. SO KURZ WIE MÖGLICH, so lang wie nötig. Markdown: `inline-code` für Bezeichner, examples als Codeblock mit Sprachangabe (```sprache), beginnend mit kurzem Kommentar zur Variante (z. B. `<!-- Einzelner Absatz -->`), hints nur wenn WICHTIG oder NÜTZLICH, im Telegrammstil (nur Kernaussage, z. B. "Keine Blockelemente in `<p>`."). aufgabe: EINE konkrete, in Minuten lösbare Übungsaufgabe; loesung: die knappe Musterlösung dazu. Tags/Bezeichner im Fließtext IMMER in Backticks.
- target: "description" | "examples" | "hints"
- content: fertiger Inhalt zum Einfügen. SO KURZ WIE MÖGLICH, so lang wie nötig. Markdown: `inline-code` für Bezeichner, examples als Codeblock mit Sprachangabe (```sprache), beginnend mit kurzem Kommentar zur Variante (z. B. `<!-- Einzelner Absatz -->`), hints nur wenn WICHTIG oder NÜTZLICH, im Telegrammstil (nur Kernaussage, z. B. "Keine Blockelemente in `<p>`."). Tags/Bezeichner im Fließtext IMMER in Backticks.
Gib NUR gültiges JSON aus, ohne Code-Fence, ohne weiteren Text:
{{"suggestions": [{{"text": "...", "target": "hints", "content": "..."}}]}}

View File

@@ -11,8 +11,6 @@ Erstelle GENAU EIN Element zum Stichwort:
2. description — was es ist und wozu: MAXIMAL 12 Sätze
3. examples — GENAU EIN Beispiel: KURZ und SIMPEL, wenige Zeilen Code, das Minimalbeispiel, keine Realwelt-Komplexität. Beginnt mit einem kurzen Kommentar in der Code-Syntax (z. B. `<!-- Einzelner Absatz -->`, `// Mit Default-Wert`), der die Variante benennt.
4. hints — IMMER leere Liste. Hinweise ergänzt der Nutzer später selbst. (Falls je gefordert: TELEGRAMMSTIL, max. 10 Wörter.)
5. aufgabe — GENAU EINE kleine Übungsaufgabe zum Konzept: konkret, in wenigen Minuten lösbar, prüft das Verständnis. Markdown, Code als Codeblock mit Sprachangabe.
6. loesung — die Musterlösung zur Aufgabe: knapp, nachvollziehbar, Schritt für Schritt nur wo nötig. Code als Codeblock mit Sprachangabe.
Das Element ist ATOMAR: allein verständlich, ohne dass der Leser etwas anderes gelesen hat. Benutzte Begriffe in einem Halbsatz auflösen.
@@ -23,4 +21,4 @@ Tonalität: klares Deutsch, direkt, praxisorientiert. Fachbegriffe beim ersten A
Markdown in description und examples: normale Absätze, `inline-code` für Bezeichner, Codeblöcke mit Sprachangabe (```sprache), **fett** sparsam für Kernaussagen. Keine Überschriften. Code-Beispiele IMMER als Codeblock, nie als Inline-Code. Bezeichner, Tags und Befehle (z. B. `<p>`, `git add`) im Fließtext IMMER in Backticks — nie nackt.
Gib NUR gültiges JSON aus, ohne Code-Fence, ohne weiteren Text:
{{"title": "...", "description": "...", "examples": ["```sprache\n...\n```"], "hints": [], "aufgabe": "...", "loesung": "..."}}
{{"title": "...", "description": "...", "examples": ["```sprache\n...\n```"], "hints": []}}

View File

@@ -8,7 +8,6 @@ STIL-REGELN:
2. description — was es ist und wozu: MAXIMAL 12 Sätze
3. examples — KURZ und SIMPEL: wenige Zeilen Code, Minimalbeispiel, keine Realwelt-Komplexität. Ein Beispiel pro relevanter Variante, geordnet vom Üblichen zum Speziellen. Als Codeblock mit Sprachangabe (```sprache), nie als Inline-Code. Jedes Beispiel beginnt mit einem kurzen Kommentar in der Code-Syntax (z. B. `<!-- Einzelner Absatz -->`), der die Variante benennt.
4. hints — jeder Hinweis muss WICHTIG oder NÜTZLICH sein: Stolperfalle, Merksatz oder Best Practice mit echtem Praxiswert. Selbstverständliches, Nischenwissen und Redundantes zum Element entfernen. Telegrammstil: nur die Kernaussage, Füllverben und Herleitungen streichen.
aufgabe — EINE konkrete, in Minuten lösbare Übungsaufgabe, die das Verständnis prüft. loesung — knappe, nachvollziehbare Musterlösung dazu. Beide: Code als Codeblock mit Sprachangabe.
Vorher: "Browser fügen standardmäßig vertikalen Abstand vor und nach `<p>` ein — anpassbar mit `margin`."
Nachher: "Browser-Abstand um `<p>` per `margin` anpassbar."
5. Umfang: SO LANG WIE NÖTIG und SO KURZ WIE MÖGLICH. Jedes Wort muss seinen Platz verdienen — Füllwörter, Nebensätze ohne Informationswert und Selbstverständliches streichen. Aber: Kürze nie auf Kosten der Verständlichkeit oder Korrektheit.
@@ -18,8 +17,8 @@ STIL-REGELN:
Schlage für jeden Stil-Verstoß GENAU EINE Änderung vor:
- text: kurz, was und warum (max. 12 Wörter, reiner Text)
- action: "entfernen" | "anpassen" | "hinzufuegen"
- target: "title" | "description" | "examples" | "hints" | "aufgabe" | "loesung"
- index: 0-basierte Position im AKTUELLEN examples- bzw. hints-Array (bei title/description/aufgabe/loesung: null; bei hinzufuegen: null)
- target: "title" | "description" | "examples" | "hints"
- index: 0-basierte Position im AKTUELLEN examples- bzw. hints-Array (bei title/description: null; bei hinzufuegen: null)
- content: der neue/vollständige Inhalt (bei entfernen: leer)
"entfernen" nur für examples/hints. "hinzufuegen" sparsam — nur wenn eine Stil-Regel es verlangt (z. B. fehlender Varianten-Kommentar gehört zu "anpassen", nicht "hinzufuegen"). Erfüllt etwas die Regeln schon: NICHT anfassen.

View File

@@ -13,7 +13,7 @@ Prüfe JEDEN Kandidaten kritisch:
1. WICHTIG? Muss ein Lerner das wissen? Nice-to-haves und Nischenwissen ablehnen.
2. REDUNDANT? Steckt die Info schon im Element oder in einem anderen Kandidaten? Ablehnen bzw. Duplikate zusammenführen.
3. KORREKT? Fachlich falsch oder irreführend → ablehnen.
4. PASST das target ("description" | "examples" | "hints" | "aufgabe" | "loesung")? Sonst korrigieren. Höchstens EIN Vorschlag je für "aufgabe" und "loesung".
4. PASST das target ("description" | "examples" | "hints")? Sonst korrigieren.
Behalte nur Kandidaten, die alle Prüfungen bestehen. Verbessere dabei content auf die Stil-Regeln: SO LANG WIE NÖTIG und SO KURZ WIE MÖGLICH; `inline-code` für Bezeichner; examples als Codeblock mit Sprachangabe und kurzem Varianten-Kommentar; hints im Telegrammstil (nur Kernaussage, Kürze nie auf Kosten der Verständlichkeit).