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.
87 lines
2.9 KiB
C#
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();
|
|
}
|
|
}
|