Files
LehrerApp/LehrerApp.Desktop/Services/AiSettingsService.cs
admin 8495e1b8d0 KI-gestützte Planungsunterstützung (4.5.9) + Kompetenzkatalog-Import (8.1.2)
KI-Unterstützung: neuer Einstellungen-Tab (Anmeldung, Guthaben) und Button im
Planungs-Tab, der Einheiten+Stunden als JSON an ein neues PHP-Backend (ai-backend/)
sendet und die Antwort als prüfbare Vorschlagsliste zurückbringt. Provider-Aufruf,
Guthabenverwaltung und Abrechnung nach echten Token-Kosten laufen serverseitig, der
Desktop-Client sieht nie einen LLM-API-Key. Zentral abgesichert: eine von der KI
zurückgegebene Stunden-Id, die zu keiner echten Lesson der Einheit passt, wird nie
als Update übernommen, sondern immer als neue Stunde behandelt.

Kompetenzkatalog-Import (8.1.2): JSON-Export/Import für Kompetenzkataloge.
2026-08-16 01:31:14 +02:00

87 lines
2.9 KiB
C#

using LehrerApp.Sync.Crypto;
using System.Text.Json;
namespace LehrerApp.Desktop.Services;
internal class AiSettingsConfig
{
public bool Enabled { get; set; }
public string Username { get; set; } = "";
public string? EncryptedToken { get; set; }
}
/// <summary>
/// Einstellungen für die KI-gestützte Planungsunterstützung (TODO 4.5.9). Liegt in
/// LehrerApp.Desktop statt LehrerApp.Core, weil die Token-Verschlüsselung <see cref="SyncCrypto"/>
/// aus LehrerApp.Sync nutzt — Core bleibt bewusst frei von Abhängigkeiten außerhalb von .NET
/// selbst (siehe CLAUDE.md), Sync hängt von Core ab, nicht umgekehrt.
///
/// Das Passwort wird nie persistiert, nur das nach erfolgreichem Login vom Backend ausgestellte
/// Bearer-Token — und auch das nur verschlüsselt (AES-256-GCM über SyncCrypto, gleicher
/// Mechanismus wie beim Sync-Schlüssel). Der Schlüssel selbst liegt dateirechte-geschützt
/// (chmod 600 unter Unix) neben der Einstellungsdatei — kein Betriebssystem-Schlüsselbund, aber
/// deutlich besser als die bisherige Klartext-Ablage der Sync-Server-URL.
/// </summary>
public class AiSettingsService
{
private readonly string _configPath;
private readonly string _keyPath;
private readonly byte[] _tokenKey;
private AiSettingsConfig _config;
public bool Enabled => _config.Enabled;
public string Username => _config.Username;
public bool IsLoggedIn => _config.EncryptedToken is not null;
public AiSettingsService(string appDataPath)
{
_configPath = Path.Combine(appDataPath, "ai-settings.json");
_keyPath = Path.Combine(appDataPath, "ai-token.key");
_tokenKey = SyncCrypto.LoadKey(_keyPath) ?? GenerateAndSaveKey();
_config = Load();
}
public void SetEnabled(bool enabled)
{
_config.Enabled = enabled;
Save();
}
public void SetCredentialsAndToken(string username, string token)
{
_config.Username = username;
_config.EncryptedToken = SyncCrypto.EncryptObject(token, _tokenKey);
Save();
}
public string? GetToken() =>
_config.EncryptedToken is null ? null : SyncCrypto.DecryptObject<string>(_config.EncryptedToken, _tokenKey);
public void Logout()
{
_config.EncryptedToken = null;
Save();
}
private byte[] GenerateAndSaveKey()
{
var key = SyncCrypto.GenerateKey();
SyncCrypto.SaveKey(key, _keyPath);
return key;
}
private void Save() => File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
private AiSettingsConfig Load()
{
try
{
if (File.Exists(_configPath))
return JsonSerializer.Deserialize<AiSettingsConfig>(File.ReadAllText(_configPath))
?? new AiSettingsConfig();
}
catch { /* beschädigte Konfiguration -> Standardwert */ }
return new AiSettingsConfig();
}
}