KI-Feature 4.5.20: Medienvorschlag + Zwischenablage-Prompt für externe Erzeugung
Die KI liefert je Verlaufsplan-Phase optional einen kurzen Vorschlag, was ein Medium/Material (z.B. Tafelbild) zeigen sollte (AiPhaseStep.MaterialSuggestion), nur wenn das über das ohnehin genannte Material hinaus einen Mehrwert hätte. Bewusst nicht ins Domänenmodell übernommen (keine Migration nötig) — nur für die Review-Anzeige relevant. AiPlanningService.BuildMaterialPrompt baut daraus rein lokal (kein weiterer KI-Aufruf) einen vollständigen Prompt aus Fach/Stufe/Gruppe, Einheit, Stunde und Phase. Im AiAssistDialog landet er per Klick in der Zwischenablage, zum Einfügen in eine separate Claude-Sitzung für die eigentliche Materialerzeugung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,12 @@ public class AiPhaseStep
|
|||||||
// Name statt Guid: LLMs erfinden/verändern Guids unzuverlässig, ein Name ist beim Import
|
// Name statt Guid: LLMs erfinden/verändern Guids unzuverlässig, ein Name ist beim Import
|
||||||
// gegen den Katalog abgleichbar (kein Treffer -> null = Hauptweg, kein Fehler).
|
// gegen den Katalog abgleichbar (kein Treffer -> null = Hauptweg, kein Fehler).
|
||||||
public string? AlternativePathName { get; set; }
|
public string? AlternativePathName { get; set; }
|
||||||
|
// Kurzer Vorschlag für ein konkretes Medium/Material zu dieser Phase (z.B. Tafelbild,
|
||||||
|
// Arbeitsblatt), nur gesetzt, wenn eines wirklich sinnvoll wäre — nicht bei jeder Phase.
|
||||||
|
// Wird NICHT in Lesson.Phases übernommen (nur für die Review-Anzeige/Prompt-Erzeugung
|
||||||
|
// relevant, siehe AiPlanningService.BuildMaterialPrompt), daher kein Feld auf
|
||||||
|
// LessonPhaseStep nötig.
|
||||||
|
public string? MaterialSuggestion { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AiPlanningResponse
|
public class AiPlanningResponse
|
||||||
|
|||||||
@@ -205,6 +205,50 @@ public sealed class AiPlanningServiceTests
|
|||||||
Assert.Contains(service.DescribeChanges(existing, proposed), d => d.Contains("Verlaufsplan"));
|
Assert.Contains(service.DescribeChanges(existing, proposed), d => d.Contains("Verlaufsplan"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildMaterialPrompt_EnthaeltKontextUndVorschlag()
|
||||||
|
{
|
||||||
|
var subject = new Subject { Name = "Chemie" };
|
||||||
|
var group = new LearningGroup { Name = "9c", SubjectId = subject.Id, GradeLevel = 9 };
|
||||||
|
var unit = new Unit { GroupId = group.Id, Title = "Redoxreaktionen" };
|
||||||
|
var lesson = new AiLesson { Topic = "Elektrolyse", Date = new DateOnly(2026, 3, 17), Phases = [] };
|
||||||
|
var phase = new AiPhaseStep
|
||||||
|
{
|
||||||
|
Name = "Erarbeitung", DurationMinutes = 20, Activity = "Experiment auswerten",
|
||||||
|
Material = "AB003", MaterialSuggestion = "Tafelbild mit dem Aufbau der Elektrolysezelle.",
|
||||||
|
};
|
||||||
|
|
||||||
|
var service = Build(new FakeLessons(), new FakeGroups([group]), new FakeSubjects([subject]),
|
||||||
|
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
|
||||||
|
|
||||||
|
var prompt = service.BuildMaterialPrompt(unit, lesson, phase);
|
||||||
|
|
||||||
|
Assert.Contains("Chemie", prompt);
|
||||||
|
Assert.Contains("9c", prompt);
|
||||||
|
Assert.Contains("Redoxreaktionen", prompt);
|
||||||
|
Assert.Contains("Elektrolyse", prompt);
|
||||||
|
Assert.Contains("Erarbeitung", prompt);
|
||||||
|
Assert.Contains("AB003", prompt);
|
||||||
|
Assert.Contains("Tafelbild mit dem Aufbau der Elektrolysezelle.", prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildMaterialPrompt_OhneBisherigesMaterial_LaesstZeileWeg()
|
||||||
|
{
|
||||||
|
var group = new LearningGroup();
|
||||||
|
var unit = new Unit { GroupId = group.Id, Title = "T" };
|
||||||
|
var lesson = new AiLesson { Topic = "Stunde", Phases = [] };
|
||||||
|
var phase = new AiPhaseStep { Name = "Einstieg", MaterialSuggestion = "Kurzer Impuls per Bild." };
|
||||||
|
|
||||||
|
var service = Build(new FakeLessons(), new FakeGroups([group]), new FakeSubjects([]),
|
||||||
|
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
|
||||||
|
|
||||||
|
var prompt = service.BuildMaterialPrompt(unit, lesson, phase);
|
||||||
|
|
||||||
|
Assert.DoesNotContain("Bisheriges Material", prompt);
|
||||||
|
Assert.Contains("Kurzer Impuls per Bild.", prompt);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ApplyResponse_BekannteId_WirdAlsUpdateBehandelt()
|
public void ApplyResponse_BekannteId_WirdAlsUpdateBehandelt()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -249,6 +249,39 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
|
|||||||
return diffs;
|
return diffs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baut aus Einheit/Stunde/Phase und dem KI-Medienvorschlag (<see cref="AiPhaseStep.MaterialSuggestion"/>)
|
||||||
|
/// einen vollständigen, eigenständigen Prompt zum Einfügen in eine separate Claude-Sitzung —
|
||||||
|
/// die Lehrkraft kann sich das eigentliche Material (Tafelbild, Arbeitsblatt, ...) dort selbst
|
||||||
|
/// erzeugen lassen, ohne dass das über das eigene (kostenpflichtige) KI-Backend laufen muss.
|
||||||
|
/// Nur der kurze Vorschlagstext kommt von der KI, der restliche Kontext wird rein lokal aus
|
||||||
|
/// bereits vorhandenen Daten zusammengesetzt. Rein, ohne I/O.
|
||||||
|
/// </summary>
|
||||||
|
public string BuildMaterialPrompt(Unit unit, AiLesson lesson, AiPhaseStep phase)
|
||||||
|
{
|
||||||
|
var group = groups.GetById(unit.GroupId);
|
||||||
|
var subject = group?.SubjectId is { } subjectId ? subjects.GetById(subjectId) : null;
|
||||||
|
var dateText = lesson.Date?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture) ?? "noch offen";
|
||||||
|
|
||||||
|
var lines = new List<string>
|
||||||
|
{
|
||||||
|
$"Ich unterrichte {subject?.Name ?? "unbekanntes Fach"}, Klassenstufe {group?.GradeLevel ?? 0}, Gruppe {group?.Name ?? "-"}.",
|
||||||
|
$"Einheit: „{unit.Title}“",
|
||||||
|
$"Stunde: „{lesson.Topic}“ (Datum: {dateText})",
|
||||||
|
$"Phase: „{phase.Name}“, {phase.DurationMinutes} Min., Tätigkeit: {phase.Activity}",
|
||||||
|
};
|
||||||
|
if (!string.IsNullOrWhiteSpace(phase.Material))
|
||||||
|
lines.Add($"Bisheriges Material laut Planung: {phase.Material}");
|
||||||
|
lines.Add("");
|
||||||
|
lines.Add("Empfehlung aus der Unterrichtsplanung, was das Material zeigen/enthalten sollte:");
|
||||||
|
lines.Add(phase.MaterialSuggestion ?? "");
|
||||||
|
lines.Add("");
|
||||||
|
lines.Add("Bitte erstelle mir auf dieser Grundlage ein passendes Material für diese Phase " +
|
||||||
|
"(z.B. Tafelbild, Arbeitsblatt oder Visualisierung).");
|
||||||
|
|
||||||
|
return string.Join("\n", lines);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ersetzt in <paramref name="saved"/> (dem tatsächlichen Datenbankstand) jede Lesson, deren Id
|
/// Ersetzt in <paramref name="saved"/> (dem tatsächlichen Datenbankstand) jede Lesson, deren Id
|
||||||
/// auch in <paramref name="draft"/> vorkommt, durch die Entwurfsversion, und hängt Entwürfe ohne
|
/// auch in <paramref name="draft"/> vorkommt, durch die Entwurfsversion, und hängt Entwürfe ohne
|
||||||
|
|||||||
@@ -1085,6 +1085,11 @@ public partial class GenerateLessonSeriesDialogViewModel : ObservableObject
|
|||||||
|
|
||||||
// ── Dialog: KI-gestützte Planungsunterstützung (4.5.9) ───────────────────────
|
// ── Dialog: KI-gestützte Planungsunterstützung (4.5.9) ───────────────────────
|
||||||
|
|
||||||
|
/// Ein kopierbarer Prompt für ein von der KI vorgeschlagenes Material/Medium zu einer Phase (siehe
|
||||||
|
/// AiPlanningService.BuildMaterialPrompt) — gedacht zum Einfügen in eine separate Claude-Sitzung,
|
||||||
|
/// nicht über das eigene KI-Backend erzeugt.
|
||||||
|
public record MaterialPromptItem(string PhaseName, string SuggestionText, string PromptText);
|
||||||
|
|
||||||
/// Eine von der KI vorgeschlagene Stunde in der Prüfliste des Dialogs — angehakt = wird beim
|
/// Eine von der KI vorgeschlagene Stunde in der Prüfliste des Dialogs — angehakt = wird beim
|
||||||
/// "Übernehmen" mit übertragen. <see cref="AiLesson.Id"/> unterscheidet neu/geändert (siehe
|
/// "Übernehmen" mit übertragen. <see cref="AiLesson.Id"/> unterscheidet neu/geändert (siehe
|
||||||
/// AiPlanningDtos.cs), <see cref="IsNew"/> steuert hier nur die Anzeige ("Neu"/"Geändert").
|
/// AiPlanningDtos.cs), <see cref="IsNew"/> steuert hier nur die Anzeige ("Neu"/"Geändert").
|
||||||
@@ -1098,15 +1103,21 @@ public partial class AiLessonReviewItem : ObservableObject
|
|||||||
/// Aufzählung fürs UI — leer bei neuen Vorschlägen, da es dort nichts zu vergleichen gibt.
|
/// Aufzählung fürs UI — leer bei neuen Vorschlägen, da es dort nichts zu vergleichen gibt.
|
||||||
public string DiffText { get; }
|
public string DiffText { get; }
|
||||||
|
|
||||||
|
/// Medienvorschläge je Phase dieser Lesson (nur Phasen mit gesetztem
|
||||||
|
/// AiPhaseStep.MaterialSuggestion) — leer, wenn die KI für keine Phase einen Vorschlag hatte.
|
||||||
|
public IReadOnlyList<MaterialPromptItem> MaterialPrompts { get; }
|
||||||
|
|
||||||
[ObservableProperty] private bool _accepted = true;
|
[ObservableProperty] private bool _accepted = true;
|
||||||
|
|
||||||
public AiLessonReviewItem(AiLesson source, bool isNew, List<string>? fieldDiffs = null)
|
public AiLessonReviewItem(AiLesson source, bool isNew, List<string>? fieldDiffs = null,
|
||||||
|
List<MaterialPromptItem>? materialPrompts = null)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
IsNew = isNew;
|
IsNew = isNew;
|
||||||
var dateText = source.Date?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture) ?? "kein Datum";
|
var dateText = source.Date?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture) ?? "kein Datum";
|
||||||
DisplayLabel = isNew ? $"Neu: {source.Topic} ({dateText})" : $"Geändert: {source.Topic} ({dateText})";
|
DisplayLabel = isNew ? $"Neu: {source.Topic} ({dateText})" : $"Geändert: {source.Topic} ({dateText})";
|
||||||
DiffText = fieldDiffs is { Count: > 0 } ? string.Join("\n", fieldDiffs.Select(d => "• " + d)) : "";
|
DiffText = fieldDiffs is { Count: > 0 } ? string.Join("\n", fieldDiffs.Select(d => "• " + d)) : "";
|
||||||
|
MaterialPrompts = materialPrompts ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,7 +1179,11 @@ public partial class AiAssistDialogViewModel : ObservableObject
|
|||||||
// dem Nutzer eine Auswahl zu zeigen, die ApplyResponse ohnehin verwerfen würde.
|
// dem Nutzer eine Auswahl zu zeigen, die ApplyResponse ohnehin verwerfen würde.
|
||||||
if (isExisting && !AllowModifyingExisting) continue;
|
if (isExisting && !AllowModifyingExisting) continue;
|
||||||
var fieldDiffs = isExisting ? _aiPlanning.DescribeChanges(existingLessons[l.Id!.Value], l) : null;
|
var fieldDiffs = isExisting ? _aiPlanning.DescribeChanges(existingLessons[l.Id!.Value], l) : null;
|
||||||
ReviewItems.Add(new AiLessonReviewItem(l, isNew: !isExisting, fieldDiffs));
|
var materialPrompts = l.Phases
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p.MaterialSuggestion))
|
||||||
|
.Select(p => new MaterialPromptItem(p.Name, p.MaterialSuggestion!, _aiPlanning.BuildMaterialPrompt(_unit, l, p)))
|
||||||
|
.ToList();
|
||||||
|
ReviewItems.Add(new AiLessonReviewItem(l, isNew: !isExisting, fieldDiffs, materialPrompts));
|
||||||
}
|
}
|
||||||
Summary = response.Summary;
|
Summary = response.Summary;
|
||||||
HasResults = true;
|
HasResults = true;
|
||||||
|
|||||||
@@ -25,6 +25,19 @@
|
|||||||
<TextBlock Text="{Binding DiffText}" FontSize="11" Opacity="0.6" TextWrapping="Wrap"
|
<TextBlock Text="{Binding DiffText}" FontSize="11" Opacity="0.6" TextWrapping="Wrap"
|
||||||
Margin="24,0,0,0"
|
Margin="24,0,0,0"
|
||||||
IsVisible="{Binding DiffText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding DiffText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding MaterialPrompts}" Margin="24,2,0,0">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:MaterialPromptItem">
|
||||||
|
<Border Background="#14000000" CornerRadius="4" Padding="6" Margin="0,2">
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Text="{Binding SuggestionText}" FontSize="11" TextWrapping="Wrap"/>
|
||||||
|
<Button Content="📋 Prompt kopieren (extern erzeugen)" FontSize="11"
|
||||||
|
Click="OnCopyMaterialPrompt"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input.Platform;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using LehrerApp.Desktop.Services;
|
||||||
using LehrerApp.Desktop.ViewModels.Groups;
|
using LehrerApp.Desktop.ViewModels.Groups;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace LehrerApp.Desktop.Views.Groups;
|
namespace LehrerApp.Desktop.Views.Groups;
|
||||||
|
|
||||||
@@ -8,6 +11,16 @@ public partial class AiAssistDialog : Window
|
|||||||
{
|
{
|
||||||
public AiAssistDialog() => InitializeComponent();
|
public AiAssistDialog() => InitializeComponent();
|
||||||
|
|
||||||
|
private async void OnCopyMaterialPrompt(object? s, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (s is not Button { DataContext: MaterialPromptItem item }) return;
|
||||||
|
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
|
||||||
|
if (clipboard is null) return;
|
||||||
|
|
||||||
|
await clipboard.SetTextAsync(item.PromptText);
|
||||||
|
App.Services.GetRequiredService<NotificationService>().ShowSuccess("Prompt in die Zwischenablage kopiert.");
|
||||||
|
}
|
||||||
|
|
||||||
private async void OnSend(object? s, RoutedEventArgs e)
|
private async void OnSend(object? s, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is AiAssistDialogViewModel vm && vm.SendCommand.CanExecute(null))
|
if (DataContext is AiAssistDialogViewModel vm && vm.SendCommand.CanExecute(null))
|
||||||
|
|||||||
@@ -806,6 +806,32 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
|
|||||||
durch eine übernommene KI-Antwort überschreiben, falls sich die Einheit seit dem Absenden
|
durch eine übernommene KI-Antwort überschreiben, falls sich die Einheit seit dem Absenden
|
||||||
der Anfrage schon geändert hat (aktuell nicht erkannt/abgesichert). Größerer Umbau als
|
der Anfrage schon geändert hat (aktuell nicht erkannt/abgesichert). Größerer Umbau als
|
||||||
4.5.14 (Teil-Merge-UI je Phase, Änderungszeitpunkt-Vergleich) — deshalb zurückgestellt.
|
4.5.14 (Teil-Merge-UI je Phase, Änderungszeitpunkt-Vergleich) — deshalb zurückgestellt.
|
||||||
|
- [x] **4.5.20** Material-/Medienvorschlag mit Zwischenablage-Prompt für externe Erzeugung
|
||||||
|
(Nutzer-Feedback nach erstem echten Einsatz: eine KI-Stunde ist "nicht die eigene Idee",
|
||||||
|
Material/Medien wie Tafelbilder sollen die KI zumindest vorschlagen können, ohne dass die
|
||||||
|
eigentliche Erzeugung über das eigene, kostenpflichtige Backend laufen muss).
|
||||||
|
**Umsetzung:** `AiPhaseStep` bekommt ein neues optionales Feld `MaterialSuggestion` (kurzer,
|
||||||
|
konkreter Vorschlag, was ein Medium zu dieser Phase zeigen/enthalten sollte, z.B. Aufbau
|
||||||
|
eines Tafelbilds) — der Systemprompt in `plan.php` weist die KI an, das nur zu füllen, wenn
|
||||||
|
ein Medium über das ohnehin genannte `material`/`shorthand` hinaus wirklich einen Mehrwert
|
||||||
|
hätte. Bewusst **kein** neues Feld auf dem Domänenmodell `LessonPhaseStep`: der Vorschlag ist
|
||||||
|
nur für die Review-Anzeige/Prompt-Erzeugung relevant, keine Migration nötig, geht beim
|
||||||
|
"Übernehmen" bewusst nicht in die gespeicherte Lesson ein (siehe 4.5.21 für dauerhaftes
|
||||||
|
Speichern als mögliche spätere Erweiterung). `AiPlanningService.BuildMaterialPrompt` baut
|
||||||
|
daraus rein lokal (kein weiterer KI-Aufruf) einen vollständigen, eigenständigen Prompt aus
|
||||||
|
Fach/Stufe/Gruppe, Einheit, Stunde und Phase plus dem KI-Vorschlag; im `AiAssistDialog`
|
||||||
|
erscheint dieser als Kasten unter der jeweiligen Stunde mit Button "📋 Prompt kopieren
|
||||||
|
(extern erzeugen)" (Avalonia-`IClipboard`, erste Zwischenablage-Nutzung in der App), den die
|
||||||
|
Lehrkraft in eine eigene Claude-Sitzung einfügt. Kein neuer Server-Roundtrip: nur ein kurzer
|
||||||
|
zusätzlicher Textabschnitt je Phase in der ohnehin laufenden Planungsantwort.
|
||||||
|
- [ ] **4.5.21** (Zurückgestellt, Nutzer-Idee neben 4.5.20) "Schattenfeld" mit didaktischem
|
||||||
|
Hintergrund/Begründung je KI-Stunde bzw. -Phase, einklappbar/auf Nachfrage sichtbar — bei
|
||||||
|
einer KI-generierten Stunde fehlt anders als bei einer selbst geschriebenen das eigene
|
||||||
|
Vorwissen zum "Warum", das für einen sicheren Unterrichtseinsatz hilft. Zwei Bauweisen
|
||||||
|
abzuwägen: immer mitgeneriert (höhere Kosten bei jeder Anfrage, auch wenn ungenutzt) vs. nur
|
||||||
|
auf Klick nachgeladen (wiederverwendet den Nachfassen-Mechanismus aus 4.5.17, kostet nur bei
|
||||||
|
tatsächlicher Nutzung) — Letzteres passt besser zum bestehenden Guthabenmodell. Noch nicht
|
||||||
|
entschieden/umgesetzt.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -106,6 +106,13 @@ Das Feld "allowModifyingExistingLessons" in der Eingabe legt fest, was du vorsch
|
|||||||
"AB" Arbeitsblatt) — kein Fließtext.
|
"AB" Arbeitsblatt) — kein Fließtext.
|
||||||
- "durationMinutes" je Phase sollte in Summe zur für die Stunde realistischen Zeit passen
|
- "durationMinutes" je Phase sollte in Summe zur für die Stunde realistischen Zeit passen
|
||||||
(i.d.R. rund 45 Minuten je Einzelstunde, abzüglich organisatorischer Zeit).
|
(i.d.R. rund 45 Minuten je Einzelstunde, abzüglich organisatorischer Zeit).
|
||||||
|
- "materialSuggestion" je Phase (nur in deiner Antwort, nicht in der Eingabe): ein kurzer Vorschlag
|
||||||
|
(1-2 Sätze), WAS ein zu dieser Phase passendes Medium/Material konkret zeigen oder enthalten
|
||||||
|
sollte (z.B. Aufbau eines Tafelbilds, Inhalt eines Arbeitsblatts) — nicht nur "ein Tafelbild
|
||||||
|
wäre hilfreich", sondern was darauf stehen sollte. Nur setzen, wenn ein Medium über das bereits
|
||||||
|
in "material"/"shorthand" Genannte hinaus wirklich einen Mehrwert hätte, sonst weglassen (null).
|
||||||
|
Die Lehrkraft erzeugt das Medium separat selbst mit diesem Vorschlag als Grundlage — plane hier
|
||||||
|
keine Umsetzungsdetails wie Layout oder Werkzeug.
|
||||||
|
|
||||||
## Antwortformat
|
## Antwortformat
|
||||||
|
|
||||||
@@ -125,7 +132,8 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Freitext davor/danach) in gena
|
|||||||
"activity": "<Tätigkeit>",
|
"activity": "<Tätigkeit>",
|
||||||
"material": "<Material>",
|
"material": "<Material>",
|
||||||
"shorthand": "<Kurzsymbol>",
|
"shorthand": "<Kurzsymbol>",
|
||||||
"alternativePathName": "<Name aus dem übergebenen Katalog oder null für den Hauptweg>"
|
"alternativePathName": "<Name aus dem übergebenen Katalog oder null für den Hauptweg>",
|
||||||
|
"materialSuggestion": "<konkreter Medienvorschlag oder null, siehe oben>"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"homework": "<Hausaufgabe oder null>",
|
"homework": "<Hausaufgabe oder null>",
|
||||||
|
|||||||
Reference in New Issue
Block a user