feat: Planungsideen-Feld je Stunde + persistierter Materialerstellungs-Prompt
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Lesson bekommt ein neues Freitextfeld PlanningIdeas fuer grobe Ideen vor der Feinplanung, sichtbar im LessonDialog und als Kontext an die KI-Planung (Backend + MCP) durchgereicht. LessonPhaseStep.MaterialPrompt speichert den beim Uebernehmen einer KI-Stunde erzeugten Materialerstellungs-Prompt dauerhaft, statt ihn nur einmalig im AiAssistDialog anzuzeigen - im Verlaufsplan-Editor ueber einen neuen Kopieren-Button je Phase erneut nutzbar, auch ueber MCP lesbar/schreibbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,9 @@ public class AiLesson
|
||||
public List<AiPhaseStep> Phases { get; set; } = [];
|
||||
public string? Homework { get; set; }
|
||||
public string? Reflection { get; set; }
|
||||
/// Rohentwurf/erste Ideen der Lehrkraft vor der Feinplanung (siehe Lesson.PlanningIdeas) — reiner
|
||||
/// Kontext für die KI wie Homework/Reflection, wird symmetrisch übernommen/zurückgegeben.
|
||||
public string? PlanningIdeas { get; set; }
|
||||
}
|
||||
|
||||
public class AiPhaseStep
|
||||
|
||||
@@ -54,6 +54,13 @@ public class Lesson : IHasAttachments
|
||||
/// Optionaler Stundenbeginn — solange kein Stundenplan (Kapitel 4.3) existiert, manuell
|
||||
/// gepflegt. Dient nur der abgeleiteten Uhrzeit-Anzeige je Phase in <see cref="Phases"/>.
|
||||
public TimeOnly? StartTime { get; set; }
|
||||
/// Grobe Ideen/erster Entwurf, festgehalten lange bevor der Verlaufsplan (<see cref="Phases"/>)
|
||||
/// feingeplant wird (Nutzer-Feedback: die eigentliche Ideenfindung liegt zeitlich oft weit vor
|
||||
/// der Feinplanung) — bewusst ein eigenes Feld statt Zweckentfremdung von <see cref="Reflection"/>
|
||||
/// (die ist nach der Stunde) oder <see cref="Unit.Notes"/> (die ist auf Einheitenebene, nicht je
|
||||
/// Stunde). Wird auch der KI-Planungsunterstützung (4.5.9) als Kontext mitgegeben, damit erste
|
||||
/// eigene Ideen bei der KI-gestützten Weiterplanung nicht erneut abgetippt werden müssen.
|
||||
public string? PlanningIdeas { get; set; }
|
||||
public List<LessonPhaseStep> Phases { get; set; } = [];
|
||||
public TeachingTimelineState? TeachingTimeline { get; set; }
|
||||
public string? Homework { get; set; }
|
||||
@@ -95,6 +102,14 @@ public class LessonPhaseStep
|
||||
public string Activity { get; set; } = "";
|
||||
public string Material { get; set; } = "";
|
||||
public string Shorthand { get; set; } = "";
|
||||
/// Der über <see cref="AiPlanning.AiPlanningService.BuildMaterialPrompt"/> erzeugte, vollständige
|
||||
/// Prompt für diese Phase (4.5.20), sofern die KI beim letzten "Übernehmen" einen Medienvorschlag
|
||||
/// gemacht hatte — anders als der reine Vorschlagstext (<c>AiPhaseStep.MaterialSuggestion</c>,
|
||||
/// nur transient während der Review) bewusst hier persistiert (Nutzer-Feedback: der Prompt soll
|
||||
/// auch nach dem Übernehmen noch abrufbar/erneut kopierbar bleiben, statt nur einmalig im
|
||||
/// Review-Dialog verfügbar zu sein). Rein informativ, kein Datenmodell-Bezug zu einem separaten
|
||||
/// "Material"-Konzept, das es weiterhin nicht gibt — siehe TODO.md 4.5.26.
|
||||
public string? MaterialPrompt { get; set; }
|
||||
/// <summary>
|
||||
/// null = Hauptweg. Sonst Verweis auf einen benannten <see cref="AlternativeLessonPath"/> aus
|
||||
/// dem Katalog, über den Phasen alternativer Unterrichtsverläufe zusammengehören —
|
||||
|
||||
@@ -188,6 +188,7 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
|
||||
StartTime = l.StartTime,
|
||||
Homework = l.Homework,
|
||||
Reflection = l.Reflection,
|
||||
PlanningIdeas = l.PlanningIdeas,
|
||||
Phases = ToAiPhases(l.Phases, pathNames),
|
||||
})
|
||||
.ToList();
|
||||
@@ -270,6 +271,9 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
|
||||
if (!string.Equals(existing.Reflection, proposed.Reflection, StringComparison.Ordinal))
|
||||
diffs.Add("Reflexion geändert");
|
||||
|
||||
if (!string.Equals(existing.PlanningIdeas, proposed.PlanningIdeas, StringComparison.Ordinal))
|
||||
diffs.Add("Planungsideen geändert");
|
||||
|
||||
var pathNames = altPaths.GetAll().ToDictionary(p => p.Id, p => p.Name);
|
||||
var existingPhases = ToAiPhases(existing.Phases, pathNames);
|
||||
if (!PhasesEqual(existingPhases, proposed.Phases))
|
||||
@@ -586,6 +590,7 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
|
||||
StartTime = ai.StartTime,
|
||||
Homework = ai.Homework,
|
||||
Reflection = ai.Reflection,
|
||||
PlanningIdeas = ai.PlanningIdeas,
|
||||
// Status bleibt bei einer Änderung erhalten — sonst würde eine bereits
|
||||
// durchgeführte Stunde durch eine KI-Anpassung stillschweigend auf "Geplant"
|
||||
// zurückgesetzt (die KI kennt/liefert diesen Status gar nicht).
|
||||
@@ -597,6 +602,10 @@ public class AiPlanningService(HttpClient http, ILessonRepository lessons,
|
||||
Activity = p.Activity,
|
||||
Material = p.Material,
|
||||
Shorthand = p.Shorthand,
|
||||
// Persistiert den Prompt (4.5.20/4.5.36), damit er nach dem Übernehmen weiterhin
|
||||
// im Stundeneditor kopierbar bleibt statt nur einmalig im Review-Dialog.
|
||||
MaterialPrompt = string.IsNullOrWhiteSpace(p.MaterialSuggestion)
|
||||
? null : BuildMaterialPrompt(unit, ai, p),
|
||||
AlternativePathId = p.AlternativePathName is { } name && pathIdsByName.TryGetValue(name, out var pathId)
|
||||
? pathId : null,
|
||||
}).ToList(),
|
||||
|
||||
@@ -24,14 +24,20 @@ public record TimeEntryDto(
|
||||
Guid Id, Guid? TaskId, string Category, Guid? GroupId, DateOnly Date,
|
||||
TimeOnly? StartTime, TimeOnly? EndTime, int DurationMinutes, string? Description);
|
||||
|
||||
public record LessonPhaseDto(Guid Id, string Name, int DurationMinutes, string Activity, string Material, string Shorthand);
|
||||
/// <summary><see cref="MaterialPrompt"/> ist der beim letzten KI-"Übernehmen" gespeicherte
|
||||
/// Materialerstellungs-Prompt (4.5.20/4.5.36) — nur gesetzt, wenn die KI für diese Phase einen
|
||||
/// Medienvorschlag gemacht hatte. Ein MCP-Client kann ihn direkt zur Materialerzeugung nutzen, ohne
|
||||
/// erst den Desktop-Dialog öffnen zu müssen.</summary>
|
||||
public record LessonPhaseDto(
|
||||
Guid Id, string Name, int DurationMinutes, string Activity, string Material, string Shorthand,
|
||||
string? MaterialPrompt);
|
||||
|
||||
public record LessonAttachmentDto(string StorageId, string FileName, long SizeBytes);
|
||||
|
||||
public record LessonDto(
|
||||
Guid Id, Guid UnitId, Guid GroupId, DateOnly Date, int? LessonNumber, string Topic,
|
||||
string? Homework, LessonStatus Status, List<string> Competencies, List<LessonPhaseDto> Phases,
|
||||
List<LessonAttachmentDto> Attachments);
|
||||
string? Homework, string? PlanningIdeas, LessonStatus Status, List<string> Competencies,
|
||||
List<LessonPhaseDto> Phases, List<LessonAttachmentDto> Attachments);
|
||||
|
||||
/// <summary>Ergebnis von "download_lesson_attachment": Inhalt Base64-kodiert, weil MCP-Tool-Antworten
|
||||
/// als JSON/Text übertragen werden. Bewusst kein Ressourcen-URI-Mechanismus (siehe Planungsdokument) -
|
||||
|
||||
@@ -188,11 +188,12 @@ public class LessonPlanTools(
|
||||
return new WriteResultDto(true, lesson.Id, "Einzelstunde gespeichert.");
|
||||
}
|
||||
|
||||
[Description("Ändert Metadaten einer bestehenden Einzelstunde (Thema, Hausaufgabe, Status, Beginn, Stundennummer) — der Verlaufsplan (Phasen) bleibt unverändert. Nur angegebene Felder werden geändert.")]
|
||||
[Description("Ändert Metadaten einer bestehenden Einzelstunde (Thema, Hausaufgabe, Planungsideen, Status, Beginn, Stundennummer) — der Verlaufsplan (Phasen) bleibt unverändert. Nur angegebene Felder werden geändert.")]
|
||||
public async Task<WriteResultDto> UpdateLesson(
|
||||
[Description("ID der Einzelstunde.")] Guid lessonId,
|
||||
[Description("Neues Thema. Unverändert lassen: weglassen.")] string? topic = null,
|
||||
[Description("Neue Hausaufgabe. Unverändert lassen: weglassen.")] string? homework = null,
|
||||
[Description("Neue Planungsideen (grober Entwurf vor der Feinplanung). Unverändert lassen: weglassen.")] string? planningIdeas = null,
|
||||
[Description("Neuer Status: Planned, Conducted, Draft, Ready oder Cancelled (ausgefallen, z.B. Exkursion/Feiertag - Alternative zu delete_lesson, wenn die Stunde als Ereignis dokumentiert bleiben soll). Unverändert lassen: weglassen.")] LessonStatus? status = null,
|
||||
[Description("Neuer Stundenbeginn, Format HH:mm. Unverändert lassen: weglassen.")] TimeOnly? startTime = null,
|
||||
[Description("Neue Stundennummer. Unverändert lassen: weglassen.")] int? lessonNumber = null,
|
||||
@@ -204,6 +205,7 @@ public class LessonPlanTools(
|
||||
var changes = new StringBuilder();
|
||||
if (topic is not null && topic != lesson.Topic) { changes.AppendLine($"Thema: „{lesson.Topic}“ → „{topic}“"); lesson.Topic = topic; }
|
||||
if (homework is not null && homework != lesson.Homework) { changes.AppendLine($"Hausaufgabe: „{lesson.Homework}“ → „{homework}“"); lesson.Homework = homework; }
|
||||
if (planningIdeas is not null && planningIdeas != lesson.PlanningIdeas) { changes.AppendLine($"Planungsideen: „{lesson.PlanningIdeas}“ → „{planningIdeas}“"); lesson.PlanningIdeas = planningIdeas; }
|
||||
if (status is not null && status != lesson.Status) { changes.AppendLine($"Status: {lesson.Status} → {status}"); lesson.Status = status.Value; }
|
||||
if (startTime is not null && startTime != lesson.StartTime) { changes.AppendLine($"Beginn: {lesson.StartTime:HH\\:mm} → {startTime:HH\\:mm}"); lesson.StartTime = startTime; }
|
||||
if (lessonNumber is not null && lessonNumber != lesson.LessonNumber) { changes.AppendLine($"Nr.: {lesson.LessonNumber} → {lessonNumber}"); lesson.LessonNumber = lessonNumber; }
|
||||
@@ -228,6 +230,7 @@ public class LessonPlanTools(
|
||||
[Description("Tätigkeit/Sozialform.")] string activity = "",
|
||||
[Description("Material.")] string material = "",
|
||||
[Description("Kurzsymbol, z.B. \"AB001->S\".")] string shorthand = "",
|
||||
[Description("Optionaler, vollständiger Prompt zur Materialerstellung für diese Phase (siehe get_lesson_plans, LessonPhaseDto.MaterialPrompt) - z.B. wenn eine externe KI-Sitzung ihn selbst formuliert hat und er zur Wiederverwendung gespeichert werden soll.")] string? materialPrompt = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var lesson = lessons.GetById(lessonId);
|
||||
@@ -240,7 +243,7 @@ public class LessonPlanTools(
|
||||
var phase = new LessonPhaseStep
|
||||
{
|
||||
Name = name, DurationMinutes = durationMinutes, Activity = activity,
|
||||
Material = material, Shorthand = shorthand,
|
||||
Material = material, Shorthand = shorthand, MaterialPrompt = materialPrompt,
|
||||
};
|
||||
lesson.Phases.Add(phase);
|
||||
lessons.Save(lesson);
|
||||
@@ -256,6 +259,7 @@ public class LessonPlanTools(
|
||||
[Description("Neue Tätigkeit. Unverändert lassen: weglassen.")] string? activity = null,
|
||||
[Description("Neues Material. Unverändert lassen: weglassen.")] string? material = null,
|
||||
[Description("Neues Kurzsymbol. Unverändert lassen: weglassen.")] string? shorthand = null,
|
||||
[Description("Neuer Prompt zur Materialerstellung (siehe get_lesson_plans, LessonPhaseDto.MaterialPrompt). Unverändert lassen: weglassen.")] string? materialPrompt = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var lesson = lessons.GetById(lessonId);
|
||||
@@ -269,6 +273,7 @@ public class LessonPlanTools(
|
||||
if (activity is not null && activity != phase.Activity) { changes.AppendLine($"Tätigkeit: „{phase.Activity}“ → „{activity}“"); phase.Activity = activity; }
|
||||
if (material is not null && material != phase.Material) { changes.AppendLine($"Material: „{phase.Material}“ → „{material}“"); phase.Material = material; }
|
||||
if (shorthand is not null && shorthand != phase.Shorthand) { changes.AppendLine($"Kürzel: „{phase.Shorthand}“ → „{shorthand}“"); phase.Shorthand = shorthand; }
|
||||
if (materialPrompt is not null && materialPrompt != phase.MaterialPrompt) { changes.AppendLine("Materialerstellungs-Prompt geändert."); phase.MaterialPrompt = materialPrompt; }
|
||||
|
||||
if (changes.Length == 0)
|
||||
return new WriteResultDto(true, phase.Id, "Keine Änderung nötig.");
|
||||
@@ -409,7 +414,7 @@ public class LessonPlanTools(
|
||||
}
|
||||
|
||||
private static LessonDto ToDto(Lesson l) => new(
|
||||
l.Id, l.UnitId, l.GroupId, l.Date, l.LessonNumber, l.Topic, l.Homework, l.Status, l.Competencies,
|
||||
l.Phases.Select(p => new LessonPhaseDto(p.Id, p.Name, p.DurationMinutes, p.Activity, p.Material, p.Shorthand)).ToList(),
|
||||
l.Id, l.UnitId, l.GroupId, l.Date, l.LessonNumber, l.Topic, l.Homework, l.PlanningIdeas, l.Status, l.Competencies,
|
||||
l.Phases.Select(p => new LessonPhaseDto(p.Id, p.Name, p.DurationMinutes, p.Activity, p.Material, p.Shorthand, p.MaterialPrompt)).ToList(),
|
||||
l.Attachments.Select(a => new LessonAttachmentDto(a.StorageId, a.FileName, a.SizeBytes)).ToList());
|
||||
}
|
||||
|
||||
@@ -737,6 +737,7 @@ public partial class LessonDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private int? _lessonNumber;
|
||||
[ObservableProperty] private string _topic = "";
|
||||
[ObservableProperty] private string _startTimeText = "";
|
||||
[ObservableProperty] private string _planningIdeas = "";
|
||||
[ObservableProperty] private string _homework = "";
|
||||
[ObservableProperty] private bool _homeworkChecked;
|
||||
[ObservableProperty] private bool _homeworkCheckDismissed;
|
||||
@@ -823,6 +824,7 @@ public partial class LessonDialogViewModel : ObservableObject
|
||||
LessonNumber = editingLesson.LessonNumber;
|
||||
Topic = editingLesson.Topic;
|
||||
StartTimeText = editingLesson.StartTime?.ToString("HH:mm") ?? "";
|
||||
PlanningIdeas = editingLesson.PlanningIdeas ?? "";
|
||||
Homework = editingLesson.Homework ?? "";
|
||||
HomeworkChecked = editingLesson.HomeworkChecked;
|
||||
HomeworkCheckDismissed = editingLesson.HomeworkCheckDismissed;
|
||||
@@ -888,6 +890,7 @@ public partial class LessonDialogViewModel : ObservableObject
|
||||
Activity = source?.Activity ?? "",
|
||||
Material = source?.Material ?? "",
|
||||
Shorthand = source?.Shorthand ?? "",
|
||||
MaterialPrompt = source?.MaterialPrompt,
|
||||
};
|
||||
item.OnChanged = RecomputeTimes;
|
||||
item.OnRemove = RemovePhase;
|
||||
@@ -1075,6 +1078,7 @@ public partial class LessonDialogViewModel : ObservableObject
|
||||
Result.LessonNumber = LessonNumber;
|
||||
Result.Topic = Topic.Trim();
|
||||
Result.StartTime = startTime;
|
||||
Result.PlanningIdeas = string.IsNullOrWhiteSpace(PlanningIdeas) ? null : PlanningIdeas.Trim();
|
||||
Result.Phases = Phases.Select(p => p.ToModel()).ToList();
|
||||
Result.Homework = string.IsNullOrWhiteSpace(Homework) ? null : Homework.Trim();
|
||||
Result.HomeworkChecked = HomeworkChecked;
|
||||
@@ -1103,6 +1107,13 @@ public partial class PhaseStepEditItem : ObservableObject
|
||||
[ObservableProperty] private string _material = "";
|
||||
[ObservableProperty] private string _shorthand = "";
|
||||
[ObservableProperty] private string _computedTimeDisplay = "";
|
||||
/// Gespeicherter Materialerstellungs-Prompt (4.5.20/4.5.36) — nur gesetzt, wenn die KI beim
|
||||
/// letzten "Übernehmen" einen Medienvorschlag für diese Phase gemacht hatte. Steuert die
|
||||
/// Sichtbarkeit des Kopieren-Buttons im Verlaufsplan-Editor.
|
||||
[ObservableProperty] private string? _materialPrompt;
|
||||
|
||||
public bool HasMaterialPrompt => !string.IsNullOrWhiteSpace(MaterialPrompt);
|
||||
partial void OnMaterialPromptChanged(string? value) => OnPropertyChanged(nameof(HasMaterialPrompt));
|
||||
|
||||
/// Checkbox-Zustand im Editor: unchecked→checked öffnet den Zuweisen-Dialog
|
||||
/// (<see cref="OnAssignAlternativePath"/>); checked→unchecked entfernt die Zuordnung.
|
||||
@@ -1162,6 +1173,7 @@ public partial class PhaseStepEditItem : ObservableObject
|
||||
Activity = Activity.Trim(),
|
||||
Material = Material.Trim(),
|
||||
Shorthand = Shorthand.Trim(),
|
||||
MaterialPrompt = MaterialPrompt,
|
||||
AlternativePathId = AlternativePathId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@
|
||||
IsVisible="{Binding TopicError, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Text="Planungsideen (Rohentwurf)" FontSize="12" Opacity="0.7"
|
||||
ToolTip.Tip="Erste, noch grobe Ideen — lange bevor der Verlaufsplan unten feingeplant wird. Fließt auch als Kontext in die KI-Unterstützung (Backend und MCP) ein."/>
|
||||
<TextBox Text="{Binding PlanningIdeas}" AcceptsReturn="True" Height="64" TextWrapping="Wrap"
|
||||
PlaceholderText="Grobe Ideen, mögliche Aufhänger, erste Materialgedanken ..."/>
|
||||
</StackPanel>
|
||||
|
||||
<Separator Margin="0,4"/>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
@@ -66,7 +73,7 @@
|
||||
</Grid>
|
||||
|
||||
<!-- Tabellenkopf -->
|
||||
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26" Margin="4,0,0,0">
|
||||
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26,26" Margin="4,0,0,0">
|
||||
<TextBlock Grid.Column="0" Text="Phase" FontSize="11" FontWeight="SemiBold" Opacity="0.6"/>
|
||||
<TextBlock Grid.Column="1" Text="Pfad" FontSize="11" FontWeight="SemiBold" Opacity="0.6"
|
||||
ToolTip.Tip="Ankreuzen, wenn diese Phase zu einem alternativen Ablauf gehört (z.B. Kurzversion bei Zeitnot) — öffnet die Zuweisung. Phasen mit demselben Ablauf werden im Verlaufsplan-Viewer gruppiert."/>
|
||||
@@ -83,7 +90,7 @@
|
||||
<DataTemplate x:DataType="vm:PhaseStepEditItem">
|
||||
<Border BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="0,0,0,1" Padding="0,8">
|
||||
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26">
|
||||
<Grid ColumnDefinitions="130,95,120,*,100,95,26,26,26,26">
|
||||
<TextBox Grid.Column="0" Text="{Binding Name}" PlaceholderText="z.B. Erarbeitung"
|
||||
VerticalAlignment="Top" Margin="0,0,6,0"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding HasAlternativePath}"
|
||||
@@ -110,11 +117,14 @@
|
||||
FilterMode="Contains" MinimumPrefixLength="0" VerticalAlignment="Top"
|
||||
Margin="0,0,6,0" PlaceholderText="z.B. AB001->S, Plenum, LDE"
|
||||
ToolTip.Tip="Freitext für den schnellen Überblick — mal ein Materialfluss-Pfeil (AB001->S), mal nur eine Sozialform (Plenum, LDE)."/>
|
||||
<Button Grid.Column="6" Content="↑" Command="{Binding MoveUpCommand}" Padding="4,2"
|
||||
<Button Grid.Column="6" Content="📋" Padding="4,2" VerticalAlignment="Top" Margin="0,0,2,0"
|
||||
IsVisible="{Binding HasMaterialPrompt}" Click="OnCopyMaterialPrompt"
|
||||
ToolTip.Tip="Gespeicherten Prompt zur Materialerstellung erneut in die Zwischenablage kopieren."/>
|
||||
<Button Grid.Column="7" Content="↑" Command="{Binding MoveUpCommand}" Padding="4,2"
|
||||
VerticalAlignment="Top" ToolTip.Tip="Nach oben"/>
|
||||
<Button Grid.Column="7" Content="↓" Command="{Binding MoveDownCommand}" Padding="4,2"
|
||||
<Button Grid.Column="8" Content="↓" Command="{Binding MoveDownCommand}" Padding="4,2"
|
||||
VerticalAlignment="Top" Margin="2,0,0,0" ToolTip.Tip="Nach unten"/>
|
||||
<Button Grid.Column="8" Content="✕" Command="{Binding RemoveCommand}" Padding="4,2"
|
||||
<Button Grid.Column="9" Content="✕" Command="{Binding RemoveCommand}" Padding="4,2"
|
||||
VerticalAlignment="Top" Margin="2,0,0,0" ToolTip.Tip="Entfernen"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input.Platform;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Platform.Storage;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
@@ -48,6 +49,20 @@ public partial class LessonDialog : Window
|
||||
Close(false);
|
||||
}
|
||||
|
||||
/// Kopiert den beim letzten KI-"Übernehmen" gespeicherten Materialerstellungs-Prompt (4.5.36,
|
||||
/// Nachtrag zu 4.5.20) erneut in die Zwischenablage — derselbe Mechanismus wie im AiAssistDialog,
|
||||
/// hier aber für einen bereits gespeicherten, nicht mehr nur transienten Vorschlag.
|
||||
private async void OnCopyMaterialPrompt(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button { DataContext: PhaseStepEditItem item } || string.IsNullOrWhiteSpace(item.MaterialPrompt))
|
||||
return;
|
||||
|
||||
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
|
||||
if (clipboard is null) return;
|
||||
await clipboard.SetTextAsync(item.MaterialPrompt);
|
||||
App.Services.GetRequiredService<NotificationService>().ShowSuccess("Prompt in die Zwischenablage kopiert.");
|
||||
}
|
||||
|
||||
private async void OnAddAttachment(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not LessonDialogViewModel vm) return;
|
||||
|
||||
@@ -2916,6 +2916,40 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
|
||||
- Kein zusätzliches Feature wie Windows-Autostart für LehrerApp.Desktop selbst oder ein
|
||||
Tray-Icon (auf Nachfrage bewusst nicht Teil dieser Änderung) — die Bridge löst das Problem
|
||||
rein auf Protokollebene, unabhängig davon, wann der Nutzer LehrerApp tatsächlich startet.
|
||||
- [x] **4.5.35** Freitextfeld "Planungsideen" je Stunde (2026-09-14, Nutzer-Feedback): erste, noch
|
||||
grobe Ideen zu einer Stunde entstehen erfahrungsgemäß lange bevor der Verlaufsplan (4.2.2)
|
||||
feingeplant wird — dafür gab es bisher kein Feld, nur `Reflection` (nach der Stunde) und
|
||||
`Unit.Notes` (auf Einheiten- statt Stundenebene). Neues `Lesson.PlanningIdeas` (`string?`),
|
||||
im [LessonDialog](LehrerApp.Desktop/Views/Groups/LessonDialog.axaml) direkt unter "Thema" als
|
||||
mehrzeiliges Freitextfeld, noch vor dem Verlaufsplan — bewusste Platzierung, damit die Idee
|
||||
zuerst und die Feinplanung erst danach kommt. Keine Migration nötig (LiteDB, neues Feld).
|
||||
Auch als Kontext für die KI-Planungsunterstützung (4.5.9) durchgereicht, wie von der
|
||||
Lehrkraft gewünscht ("auch nützlich für die KI-gestützte Weiterplanung"): `AiLesson`/
|
||||
`AiUnitContext` (Core/AiPlanning) bekommen ein neues, zu `Homework`/`Reflection` symmetrisches
|
||||
Feld `planningIdeas` (gelesen in `AiPlanningService.BuildContext`, übernommen in
|
||||
`ApplyResponse`, im Planungsdiff berücksichtigt in `DescribeChanges`); `ai-backend/plan.php`
|
||||
bekommt das Feld in Ein- und Ausgabeschema samt Hinweis an die KI, es als starken Kontext zu
|
||||
nutzen und unverändert zurückzugeben, außer die Anweisung verlangt ausdrücklich eine
|
||||
Überarbeitung. Für die MCP-gestützte Weiterplanung (4.5.28) zusätzlich in `LessonDto`
|
||||
(Services/Mcp/Tools/Dto.cs) exponiert und über einen neuen optionalen `planningIdeas`-
|
||||
Parameter von `update_lesson` (LessonPlanTools.cs) schreibbar — bewusst kein neues, eigenes
|
||||
MCP-Tool nur für dieses eine Feld, passt in das bestehende kleinteilige Muster.
|
||||
- [x] **4.5.36** Materialerstellungs-Prompt bleibt nach dem Übernehmen erhalten (2026-09-14,
|
||||
Nachtrag zu 4.5.20, Nutzer-Feedback): der in 4.5.20 gebaute Prompt war bisher nur im
|
||||
`AiAssistDialog` (Review vor dem Speichern) kopierbar — einmal übernommen, war er weg, obwohl
|
||||
die Lehrkraft ihn oft erst später in einer externen KI-Sitzung tatsächlich einlöst. Neues
|
||||
`LessonPhaseStep.MaterialPrompt` (`string?`, keine Migration nötig): `AiPlanningService.
|
||||
ApplyResponse` befüllt es automatisch mit dem von `BuildMaterialPrompt` erzeugten Text, sobald
|
||||
die Phase einen `MaterialSuggestion`-Vorschlag hatte — bewusst weiterhin kein separates
|
||||
"Material"-Domänenmodell (siehe 4.5.26), nur ein zusätzliches Feld an der bestehenden Zeile.
|
||||
Im [LessonDialog](LehrerApp.Desktop/Views/Groups/LessonDialog.axaml) erscheint dafür in der
|
||||
Verlaufsplan-Tabelle ein neuer Button "📋" je Phasenzeile, nur sichtbar wenn ein Prompt
|
||||
gespeichert ist (`PhaseStepEditItem.HasMaterialPrompt`) — derselbe Zwischenablage-Mechanismus
|
||||
wie im `AiAssistDialog` (`LessonDialog.axaml.cs:OnCopyMaterialPrompt`). Für die MCP-gestützte
|
||||
Weiterplanung zusätzlich in `LessonPhaseDto.MaterialPrompt` gelesen und über einen neuen
|
||||
optionalen Parameter an `add_lesson_phase`/`update_lesson_phase` (LessonPlanTools.cs)
|
||||
schreibbar, damit auch ein extern (z.B. in Claude Desktop) formulierter Prompt zur
|
||||
Wiederverwendung gespeichert werden kann, nicht nur ein vom eigenen KI-Backend erzeugter.
|
||||
|
||||
**Wichtige Abweichung von der ursprünglichen Planung (5.2):** Vor der Umsetzung zeigte sich,
|
||||
dass 5.2 wie ursprünglich beschrieben eine zweite, parallele Fehlzeiten-Erfassung neben dem
|
||||
|
||||
@@ -39,7 +39,8 @@ Unterricht einsetzen kann, auch wenn sie nicht ihre eigene Idee war.
|
||||
"phases": [
|
||||
{ "name": "<Phasenname>", "durationMinutes": <Zahl>, "activity": "<Tätigkeit>", "material": "<Material>", "shorthand": "<Kurzsymbol>" }
|
||||
],
|
||||
"homework": "<Hausaufgabe oder null>", "reflection": "<Reflexion oder null>"
|
||||
"homework": "<Hausaufgabe oder null>", "reflection": "<Reflexion oder null>",
|
||||
"planningIdeas": "<grobe Ideen der Lehrkraft vor der Feinplanung oder null, nur Lesekontext>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -65,7 +65,8 @@ Die Eingabe hat exakt diese Struktur:
|
||||
}
|
||||
],
|
||||
"homework": "<Hausaufgabe oder null>",
|
||||
"reflection": "<Reflexion oder null>"
|
||||
"reflection": "<Reflexion oder null>",
|
||||
"planningIdeas": "<Grobe Ideen/Rohentwurf der Lehrkraft vor der Feinplanung oder null>"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -111,6 +112,11 @@ Stunde zur Prüfung, alles andere würde ihr gar nicht angezeigt.
|
||||
"AB" Arbeitsblatt) — kein Fließtext.
|
||||
- "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).
|
||||
- "planningIdeas" einer Stunde enthält oft schon vor der Feinplanung festgehaltene grobe Ideen der
|
||||
Lehrkraft (Stichpunkte, erste Gedanken, mögliche Aufhänger) — nutze das, wo vorhanden, als starken
|
||||
Hinweis für Thema/Verlaufsplan dieser Stunde, ohne jedes Stichwort wörtlich übernehmen zu müssen.
|
||||
Gib das Feld in deiner Antwort unverändert zurück, außer die Anweisung der Lehrkraft verlangt
|
||||
ausdrücklich eine Überarbeitung dieser Ideen selbst.
|
||||
- "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
|
||||
@@ -142,7 +148,8 @@ Antworte AUSSCHLIESSLICH mit gültigem JSON (kein Freitext davor/danach) in gena
|
||||
}
|
||||
],
|
||||
"homework": "<Hausaufgabe oder null>",
|
||||
"reflection": "<Reflexion oder null>"
|
||||
"reflection": "<Reflexion oder null>",
|
||||
"planningIdeas": "<unverändert aus der Eingabe übernommen, siehe oben, oder null>"
|
||||
}
|
||||
],
|
||||
"summary": "<kurze menschenlesbare Zusammenfassung, was du getan hast>"
|
||||
|
||||
@@ -129,6 +129,15 @@ statt der typisierten `Lessons`-Collection — nach jeder Modelländerung kennt
|
||||
`Lesson`-Klasse die alten Feldnamen nicht mehr, ein Zugriff darüber hätte sie beim Deserialisieren
|
||||
bereits verworfen, bevor sie gelesen werden können.
|
||||
|
||||
`LessonPhaseStep.MaterialPrompt` (4.5.36, Nachtrag zu 4.5.20) ist kein von Hand gepflegtes Feld:
|
||||
`AiPlanningService.ApplyResponse` befüllt es automatisch mit dem vollständigen, lokal erzeugten
|
||||
Prompt (`BuildMaterialPrompt`), sobald die KI beim letzten "Übernehmen" für diese Phase einen
|
||||
Medienvorschlag gemacht hatte — ursprünglich (4.5.20) bewusst NICHT persistiert, weil der Vorschlag
|
||||
nur für die Review-Anzeige gedacht war; Nutzer-Feedback nach erstem Einsatz war, dass der Prompt
|
||||
auch nach dem Übernehmen noch abrufbar bleiben soll. Kein neues "Material"-Domänenmodell dafür (das
|
||||
gibt es weiterhin nicht, siehe 4.5.26) — der Prompt hängt einfach als zusätzliches Freitextfeld an
|
||||
der bereits bestehenden `LessonPhaseStep`-Zeile.
|
||||
|
||||
## Eindeutige Schlüssel
|
||||
|
||||
Die Datenbank schützt folgende Kombinationen mit eindeutigen Indizes:
|
||||
|
||||
Reference in New Issue
Block a user