AuthorBuddy/Components/Pages/Settings.razor

283 lines
12 KiB
Text
Raw Normal View History

2026-05-14 12:31:35 +02:00
@page "/settings"
2026-05-21 11:04:42 +02:00
@attribute [Authorize]
2026-05-16 16:13:35 +02:00
@inject ISettingsService SettingsService
@inject IThemeService ThemeService
2026-05-14 12:31:35 +02:00
@inject IOllamaService OllamaService
@inject NavigationManager Navigation
@inject IJSRuntime JSRuntime
@inject IProjectService ProjectService
2026-05-20 07:27:41 +02:00
<PageTitle>@T("Einstellungen", "Settings") - Author Buddy</PageTitle>
2026-05-14 12:31:35 +02:00
<div class="settings-page">
2026-05-17 07:27:18 +02:00
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Benutzer", "User")</h3>
2026-05-17 07:27:18 +02:00
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="userSelect">@T("Aktiver Benutzer:", "Active user:")</label>
2026-05-17 07:27:18 +02:00
<select id="userSelect" value="@selectedUserId" @onchange="SwitchUser">
@foreach (var u in users)
{
<option value="@u.Id">@u.Name</option>
}
</select>
</div>
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="newUserName">@T("Neuer Benutzer:", "New user:")</label>
2026-05-17 07:27:18 +02:00
<div style="display: flex; gap: 8px;">
2026-05-20 07:27:41 +02:00
<input id="newUserName" @bind="newUserName" placeholder="@T("Name", "Name")" style="flex: 1;" />
<button class="btn btn-secondary" @onclick="AddUser">@T("Hinzuf\u00fcgen", "Add")</button>
2026-05-17 07:27:18 +02:00
</div>
</div>
</div>
2026-05-21 11:04:42 +02:00
<div class="settings-section" style="margin-bottom:16px;">
<button class="btn btn-secondary" @onclick='() => Navigation.NavigateTo("/setup-2fa")'>@T("2FA einrichten", "Set up 2FA")</button>
</div>
2026-05-16 16:13:35 +02:00
<div class="settings-layout">
2026-05-20 07:27:41 +02:00
<div class="settings-column" >
2026-05-16 16:13:35 +02:00
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Modelle", "Models")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="ragModel">@T("RAG-Modell (Faktenabfrage):", "RAG model (facts):")</label>
2026-05-16 16:13:35 +02:00
<select id="ragModel" @bind="ragModel">
@if (availableModels.Count == 0)
{
2026-05-20 07:27:41 +02:00
<option value="">@T("Keine Modelle gefunden", "No models found")</option>
2026-05-16 16:13:35 +02:00
}
@foreach (var m in availableModels)
{
<option value="@m.Name">@m.Name</option>
}
</select>
</div>
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="styleModel">@T("Stil-Modell (Lektorat):", "Style model (editing):")</label>
2026-05-16 16:13:35 +02:00
<select id="styleModel" @bind="styleModel">
@if (availableModels.Count == 0)
{
2026-05-20 07:27:41 +02:00
<option value="">@T("Keine Modelle gefunden", "No models found")</option>
2026-05-16 16:13:35 +02:00
}
@foreach (var m in availableModels)
{
<option value="@m.Name">@m.Name</option>
}
</select>
</div>
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="spellingModel">@T("Rechtschreibpr\u00fcfungs-Modell:", "Spell check model:")</label>
2026-05-16 16:13:35 +02:00
<select id="spellingModel" @bind="spellingModel">
@if (availableModels.Count == 0)
{
2026-05-20 07:27:41 +02:00
<option value="">@T("Keine Modelle gefunden", "No models found")</option>
2026-05-16 16:13:35 +02:00
}
@foreach (var m in availableModels)
{
<option value="@m.Name">@m.Name</option>
}
</select>
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Ollama Server", "Ollama Server")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="ollamaUrl">@T("Server-URL:", "Server URL:")</label>
2026-05-16 16:13:35 +02:00
<input id="ollamaUrl" @bind="ollamaUrl" placeholder="http://localhost:11434" />
2026-05-20 07:27:41 +02:00
<div class="form-hint">@T("Adresse des Ollama-Servers", "Ollama server address")</div>
2026-05-16 16:13:35 +02:00
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Design", "Appearance")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label for="themeSelect">@T("Theme:", "Theme:")</label>
2026-05-16 16:13:35 +02:00
<select id="themeSelect" @bind="selectedTheme">
2026-05-20 07:27:41 +02:00
<option value="light">@T("Hell", "Light")</option>
<option value="dark">@T("Dunkel", "Dark")</option>
<option value="w95">W95 @T("(Retro)", "(Retro)")</option>
</select>
</div>
</div>
<div class="settings-section">
<h3 class="settings-section-title">@T("Sprache", "Language")</h3>
<div class="form-group">
<label for="languageSelect">@T("Sprache:", "Language:")</label>
<select id="languageSelect" @bind="selectedLanguage">
<option value="de">Deutsch</option>
<option value="en">English</option>
2026-05-16 16:13:35 +02:00
</select>
</div>
</div>
2026-05-14 12:31:35 +02:00
</div>
2026-05-20 07:27:41 +02:00
<div class="settings-column" >
2026-05-16 16:13:35 +02:00
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Lektor-System-Prompt", "Editor system prompt")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
<textarea id="lektorPrompt" @bind="lektorPrompt" rows="6"></textarea>
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Fact-Check-Vorlage", "Fact check template")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
<textarea id="factCheckTemplate" @bind="factCheckTemplate" rows="6"></textarea>
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Rechtschreibpr\u00fcfungs-Prompt", "Spell check prompt")</h3>
2026-05-16 16:13:35 +02:00
<div class="form-group">
<textarea id="spellingPrompt" @bind="spellingPrompt" rows="6"></textarea>
</div>
</div>
2026-05-14 12:31:35 +02:00
</div>
2026-05-20 07:27:41 +02:00
<div class="settings-column" >
2026-05-20 06:12:54 +02:00
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Advocatus Diaboli", "Advocatus Diaboli")</h3>
2026-05-20 06:12:54 +02:00
<div class="form-group">
<textarea id="advocatusDiaboliPrompt" @bind="advocatusDiaboliPrompt" rows="6"></textarea>
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("Hook-Finder", "Hook finder")</h3>
2026-05-20 06:12:54 +02:00
<div class="form-group">
<textarea id="hookFinderPrompt" @bind="hookFinderPrompt" rows="6"></textarea>
</div>
</div>
<div class="settings-section">
2026-05-20 07:27:41 +02:00
<h3 class="settings-section-title">@T("ELI5-Vereinfachung", "ELI5-Simplification")</h3>
2026-05-20 06:12:54 +02:00
<div class="form-group">
<textarea id="eli5Prompt" @bind="eli5Prompt" rows="6"></textarea>
</div>
</div>
</div>
2026-05-14 12:31:35 +02:00
</div>
2026-05-16 16:13:35 +02:00
<div class="settings-actions">
2026-05-20 07:27:41 +02:00
<button class="btn btn-primary" @onclick="SaveSettings">@T("Speichern", "Save")</button>
2026-05-16 16:13:35 +02:00
@if (!string.IsNullOrEmpty(statusMessage))
{
<span class="settings-status">@statusMessage</span>
}
2026-05-14 12:31:35 +02:00
</div>
</div>
@code {
private string ragModel = "llama3.2";
private string styleModel = "llama3.2";
private string spellingModel = "phi3:mini";
private string selectedTheme = "light";
private string ollamaUrl = "http://localhost:11434";
2026-05-16 16:13:35 +02:00
private string lektorPrompt = string.Empty;
private string factCheckTemplate = string.Empty;
private string spellingPrompt = string.Empty;
2026-05-20 06:12:54 +02:00
private string advocatusDiaboliPrompt = string.Empty;
private string hookFinderPrompt = string.Empty;
private string eli5Prompt = string.Empty;
2026-05-20 07:27:41 +02:00
private string selectedLanguage = "de";
2026-05-14 12:31:35 +02:00
private string newProjectName = string.Empty;
2026-05-17 07:27:18 +02:00
private string newUserName = string.Empty;
2026-05-14 12:31:35 +02:00
private string statusMessage = string.Empty;
2026-05-16 16:13:35 +02:00
private List<LLM_Model> availableModels = new();
2026-05-17 07:27:18 +02:00
private List<UserEntity> users = new();
private int selectedUserId;
2026-05-14 12:31:35 +02:00
2026-05-20 07:27:41 +02:00
private string T(string de, string en) => selectedLanguage == "en" ? en : de;
2026-05-14 12:31:35 +02:00
protected override async Task OnInitializedAsync()
{
2026-05-17 07:27:18 +02:00
users = await SettingsService.GetUsersAsync();
var current = await SettingsService.GetCurrentUserAsync();
selectedUserId = current.Id;
2026-05-20 06:12:54 +02:00
await LoadUserSettings();
2026-05-14 12:31:35 +02:00
try
{
var models = await OllamaService.GetLocalModelsAsync();
availableModels = models.ToList();
}
catch
{
availableModels = new();
}
}
2026-05-20 06:12:54 +02:00
private async Task LoadUserSettings()
2026-05-17 07:27:18 +02:00
{
2026-05-20 06:12:54 +02:00
ragModel = await SettingsService.GetRagModelAsync() ?? "llama3.2";
styleModel = await SettingsService.GetStyleModelAsync() ?? "llama3.2";
spellingModel = await SettingsService.GetSpellingModelAsync() ?? "phi3:mini";
selectedTheme = await SettingsService.GetThemeAsync() ?? "light";
ollamaUrl = await SettingsService.GetOllamaUrlAsync() ?? "http://localhost:11434";
lektorPrompt = await SettingsService.GetLektorSystemPromptAsync() ?? string.Empty;
factCheckTemplate = await SettingsService.GetFactCheckTemplateAsync() ?? string.Empty;
spellingPrompt = await SettingsService.GetSpellingPromptAsync() ?? string.Empty;
advocatusDiaboliPrompt = await SettingsService.GetAdvocatusDiaboliPromptAsync() ?? string.Empty;
hookFinderPrompt = await SettingsService.GetHookFinderPromptAsync() ?? string.Empty;
eli5Prompt = await SettingsService.GetEli5PromptAsync() ?? string.Empty;
2026-05-20 07:27:41 +02:00
selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de";
2026-05-17 07:27:18 +02:00
}
private async Task SwitchUser(ChangeEventArgs e)
{
var userId = int.Parse(e.Value?.ToString() ?? "0");
await SettingsService.SetCurrentUserAsync(userId);
selectedUserId = userId;
2026-05-20 06:12:54 +02:00
await LoadUserSettings();
2026-05-17 07:27:18 +02:00
}
private async Task AddUser()
{
if (string.IsNullOrWhiteSpace(newUserName)) return;
var user = await SettingsService.CreateUserAsync(newUserName);
users.Add(user);
newUserName = string.Empty;
2026-05-20 07:27:41 +02:00
statusMessage = T($"Benutzer '{user.Name}' erstellt.", $"User '{user.Name}' created.");
2026-05-17 07:27:18 +02:00
}
2026-05-14 12:31:35 +02:00
private async Task SaveSettings()
{
2026-05-16 16:13:35 +02:00
await SettingsService.SaveOllamaUrlAsync(ollamaUrl);
2026-05-20 06:12:54 +02:00
OllamaService.Reconfigure(ollamaUrl);
if (!string.IsNullOrWhiteSpace(ragModel))
{
var ok = await OllamaService.TestEmbeddingAsync(ragModel);
if (!ok)
{
2026-05-20 07:27:41 +02:00
statusMessage = selectedLanguage == "en"
? $"Model '{ragModel}' does not support embeddings. RAG model not saved."
: $"Modell '{ragModel}' unterstützt keine Embeddings. RAG-Modell nicht gespeichert.";
2026-05-20 06:12:54 +02:00
return;
}
}
2026-05-16 16:13:35 +02:00
await SettingsService.SaveRagModelAsync(ragModel);
await SettingsService.SaveStyleModelAsync(styleModel);
await SettingsService.SaveSpellingModelAsync(spellingModel);
await SettingsService.SaveThemeAsync(selectedTheme);
await SettingsService.SaveLektorSystemPromptAsync(lektorPrompt);
await SettingsService.SaveFactCheckTemplateAsync(factCheckTemplate);
await SettingsService.SaveSpellingPromptAsync(spellingPrompt);
2026-05-20 06:12:54 +02:00
await SettingsService.SaveAdvocatusDiaboliPromptAsync(advocatusDiaboliPrompt);
await SettingsService.SaveHookFinderPromptAsync(hookFinderPrompt);
await SettingsService.SaveEli5PromptAsync(eli5Prompt);
2026-05-20 07:27:41 +02:00
await SettingsService.SaveLanguageAsync(selectedLanguage);
2026-05-14 12:31:35 +02:00
2026-05-20 07:27:41 +02:00
statusMessage = T("Einstellungen gespeichert.", "Settings saved.");
2026-05-16 16:13:35 +02:00
await ThemeService.ApplyTheme(selectedTheme);
2026-05-14 12:31:35 +02:00
}
}