@page "/settings" @attribute [Authorize] @rendermode InteractiveServer @inject ISettingsService SettingsService @inject IThemeService ThemeService @inject IOllamaService OllamaService @inject OllamaSettings OllamaConfig @inject NavigationManager Navigation @inject IJSRuntime JSRuntime @inject IProjectService ProjectService @inject ILocalizationService Loc @inject IAuthService AuthService @inject IPluginManager PluginManager @Loc["page.title.settings"] - Author Buddy
@if (currentProjectName != null) { @Loc["settings.context.project"]: @currentProjectName } else { @Loc["settings.context.user"] }
@if (_isAdmin) { }
@if (_isAdmin && _activePromptTab == "admin") {

@Loc["settings.section.user"]

@Loc["settings.section.models"]

@Loc["settings.section.ollama_server"]

@if (_connectionTested) {
@(_connectionOk ? $"✓ {_connectionLatency} ms {Loc["settings.ollama.online"]}" : $"✗ {Loc["settings.ollama.offline"]}")
}
@Loc["settings.ollama.hint.url"]
Nur aktiv wenn Backend auf "llama.cpp" gesetzt ist
Nur aktiv wenn Backend auf "Ollama Cloud" gesetzt ist
Wird nur lokal gespeichert, nie ausgeloggt

@Loc["settings.section.diagnostics"]

@if (_diagnosticsResults != null) {
@foreach (var line in _diagnosticsResults) {
@line
}
}
@if (_availablePlugins.Count > 0) {

Plugins

@foreach (var p in _availablePlugins) {
@p.Name
@p.Description
@if (p.Id == "wordpress") { }
} @_pluginStatus
}

@Loc["settings.section.appearance"]

@Loc["settings.section.language"]

} @if (_activePromptTab == "novel") {

@Loc["settings.section.editor_prompt"]

@GetTempCategory(lektorTemp)
@GetTempDescription(lektorTemp)

@Loc["settings.section.factcheck_template"]

@GetTempCategory(factCheckTemp)
@GetTempDescription(factCheckTemp)

@Loc["settings.section.spellcheck_prompt"]

@GetTempCategory(spellingTemp)
@GetTempDescription(spellingTemp)

@Loc["settings.section.advocatus_diaboli"]

@GetTempCategory(advocatusDiaboliTemp)
@GetTempDescription(advocatusDiaboliTemp)

@Loc["settings.section.hook_finder"]

@GetTempCategory(hookFinderTemp)
@GetTempDescription(hookFinderTemp)

@Loc["settings.section.eli5"]

@GetTempCategory(eli5Temp)
@GetTempDescription(eli5Temp)

@Loc["settings.section.coauthor_brainstorming"]

@GetTempCategory(coAuthorTemp)
@GetTempDescription(coAuthorTemp)
} @if (_activePromptTab == "technical") {

@Loc["settings.section.tech_lastenheft"]

@GetTempCategory(techLastenheftTemp)
@GetTempDescription(techLastenheftTemp)

@Loc["settings.section.tech_pflichtenheft"]

@GetTempCategory(techPflichtenheftTemp)
@GetTempDescription(techPflichtenheftTemp)

@Loc["settings.section.tech_usecase"]

@GetTempCategory(techUseCaseTemp)
@GetTempDescription(techUseCaseTemp)

@Loc["settings.section.tech_stakeholder"]

@GetTempCategory(techStakeholderTemp)
@GetTempDescription(techStakeholderTemp)

@Loc["settings.section.tech_glossar"]

@GetTempCategory(techGlossarTemp)
@GetTempDescription(techGlossarTemp)

@Loc["settings.section.tech_qualitaet"]

@GetTempCategory(techQualitaetTemp)
@GetTempDescription(techQualitaetTemp)

@Loc["settings.section.tech_risiko"]

@GetTempCategory(techRisikoTemp)
@GetTempDescription(techRisikoTemp)
}
@if (!string.IsNullOrEmpty(statusMessage)) { @statusMessage }
@code { private string _activePromptTab = "novel"; private void SwitchToNovelTab() => _activePromptTab = "novel"; private void SwitchToTechnicalTab() => _activePromptTab = "technical"; private void SwitchToAdminTab() => _activePromptTab = "admin"; private string ragModel = "llama3.2"; private string ragBackend = "Ollama"; private string styleModel = "llama3.2"; private string styleBackend = "Ollama"; private string spellingModel = "phi3:mini"; private string spellingBackend = "Ollama"; private string selectedTheme = "light"; private string ollamaUrl = "http://localhost:11434"; private string llamaCppUrl = "http://localhost:8080"; private string lektorPrompt = string.Empty; private string factCheckTemplate = string.Empty; private string spellingPrompt = string.Empty; private string advocatusDiaboliPrompt = string.Empty; private string hookFinderPrompt = string.Empty; private string eli5Prompt = string.Empty; private string coAuthorPrompt = string.Empty; private double lektorTemp = 0.7; private double factCheckTemp = 0.3; private double spellingTemp = 0.1; private double advocatusDiaboliTemp = 0.9; private double hookFinderTemp = 0.9; private double eli5Temp = 0.5; private double coAuthorTemp = 1.0; private string techLastenheftPrompt = string.Empty; private string techPflichtenheftPrompt = string.Empty; private string techUseCasePrompt = string.Empty; private string techStakeholderPrompt = string.Empty; private string techGlossarPrompt = string.Empty; private string techQualitaetPrompt = string.Empty; private string techRisikoPrompt = string.Empty; private double techLastenheftTemp = 0.3; private double techPflichtenheftTemp = 0.3; private double techUseCaseTemp = 0.4; private double techStakeholderTemp = 0.5; private double techGlossarTemp = 0.2; private double techQualitaetTemp = 0.3; private double techRisikoTemp = 0.5; private string selectedLanguage = "de"; private string newProjectName = string.Empty; private string newUserName = string.Empty; private string statusMessage = string.Empty; private List availableRagModels = new(); private List availableStyleModels = new(); private List availableSpellingModels = new(); private List users = new(); private int selectedUserId; private string? currentProjectName; private ProjectTemplate? currentProjectTemplate; private bool _isAdmin; private bool _connectionOk; private long _connectionLatency; private bool _connectionTested; private bool _isTestingConnection; private List? _diagnosticsResults; private bool _isDiagnosing; private string _ollamaCloudUrl = string.Empty; private string _ollamaCloudApiKey = string.Empty; private List _availablePlugins = new(); private string _wpUrl = string.Empty; private string _wpUsername = string.Empty; private string _wpPassword = string.Empty; private string _pluginStatus = string.Empty; protected override async Task OnInitializedAsync() { var currentUser = AuthService.CurrentUser; _isAdmin = currentUser?.IsAdministrator == true; users = await SettingsService.GetUsersAsync(); var current = await SettingsService.GetCurrentUserAsync(); selectedUserId = current.Id; var pid = SettingsService.CurrentProjectId; if (pid != null) { var project = await ProjectService.GetProjectAsync(pid.Value); currentProjectName = project?.Name; currentProjectTemplate = project?.Template; } await LoadUserSettings(); if (_isAdmin) { await RefreshModelsForAllBackends(); _availablePlugins = PluginManager.GetPlugins().ToList(); await LoadPluginConfig(); } } private async Task LoadUserSettings() { ragModel = await SettingsService.GetRagModelAsync() ?? "llama3.2"; ragBackend = await SettingsService.GetRagBackendAsync() ?? "Ollama"; styleModel = await SettingsService.GetStyleModelAsync() ?? "llama3.2"; styleBackend = await SettingsService.GetStyleBackendAsync() ?? "Ollama"; spellingModel = await SettingsService.GetSpellingModelAsync() ?? "phi3:mini"; spellingBackend = await SettingsService.GetSpellingBackendAsync() ?? "Ollama"; selectedTheme = await SettingsService.GetThemeAsync() ?? "light"; ollamaUrl = await SettingsService.GetOllamaUrlAsync() ?? "http://localhost:11434"; llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080"; 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; coAuthorPrompt = await SettingsService.GetCoAuthorBrainstormingPromptAsync() ?? string.Empty; lektorTemp = await SettingsService.GetLektorTempAsync(); factCheckTemp = await SettingsService.GetFactCheckTempAsync(); spellingTemp = await SettingsService.GetSpellingTempAsync(); advocatusDiaboliTemp = await SettingsService.GetAdvocatusDiaboliTempAsync(); hookFinderTemp = await SettingsService.GetHookFinderTempAsync(); eli5Temp = await SettingsService.GetEli5TempAsync(); coAuthorTemp = await SettingsService.GetCoAuthorTempAsync(); techLastenheftPrompt = await SettingsService.GetTechLastenheftPromptAsync() ?? string.Empty; techPflichtenheftPrompt = await SettingsService.GetTechPflichtenheftPromptAsync() ?? string.Empty; techUseCasePrompt = await SettingsService.GetTechUseCasePromptAsync() ?? string.Empty; techStakeholderPrompt = await SettingsService.GetTechStakeholderPromptAsync() ?? string.Empty; techGlossarPrompt = await SettingsService.GetTechGlossarPromptAsync() ?? string.Empty; techQualitaetPrompt = await SettingsService.GetTechQualitaetPromptAsync() ?? string.Empty; techRisikoPrompt = await SettingsService.GetTechRisikoPromptAsync() ?? string.Empty; techLastenheftTemp = await SettingsService.GetTechLastenheftTempAsync(); techPflichtenheftTemp = await SettingsService.GetTechPflichtenheftTempAsync(); techUseCaseTemp = await SettingsService.GetTechUseCaseTempAsync(); techStakeholderTemp = await SettingsService.GetTechStakeholderTempAsync(); techGlossarTemp = await SettingsService.GetTechGlossarTempAsync(); techQualitaetTemp = await SettingsService.GetTechQualitaetTempAsync(); techRisikoTemp = await SettingsService.GetTechRisikoTempAsync(); selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de"; _ollamaCloudUrl = await SettingsService.GetOllamaCloudUrlAsync() ?? OllamaConfig.OllamaCloudUrl; _ollamaCloudApiKey = await SettingsService.GetOllamaCloudApiKeyAsync() ?? OllamaConfig.OllamaCloudApiKey; } private async Task SwitchUser(ChangeEventArgs e) { var userId = int.Parse(e.Value?.ToString() ?? "0"); selectedUserId = userId; SettingsService.SetCurrentUser(userId); await SettingsService.InitializeAsync(); await LoadUserSettings(); } private async Task AddUser() { if (string.IsNullOrWhiteSpace(newUserName)) return; var user = await SettingsService.CreateUserAsync(newUserName); users.Add(user); newUserName = string.Empty; statusMessage = string.Format(Loc["settings.status.user_created"], user.Name); } private async Task OnRagBackendChanged(ChangeEventArgs e) { ragBackend = e.Value?.ToString() ?? "Ollama"; await RefreshModelsForRag(); } private async Task OnStyleBackendChanged(ChangeEventArgs e) { styleBackend = e.Value?.ToString() ?? "Ollama"; await RefreshModelsForStyle(); } private async Task OnSpellingBackendChanged(ChangeEventArgs e) { spellingBackend = e.Value?.ToString() ?? "Ollama"; await RefreshModelsForSpelling(); } private async Task RefreshModelsForRag() { OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl; OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey; try { availableRagModels = (await OllamaService.GetModelsForBackendAsync(ragBackend)).ToList(); } catch { availableRagModels = new(); } } private async Task RefreshModelsForStyle() { OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl; OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey; try { availableStyleModels = (await OllamaService.GetModelsForBackendAsync(styleBackend)).ToList(); } catch { availableStyleModels = new(); } } private async Task RefreshModelsForSpelling() { OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl; OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey; try { availableSpellingModels = (await OllamaService.GetModelsForBackendAsync(spellingBackend)).ToList(); } catch { availableSpellingModels = new(); } } private async Task RefreshModelsForAllBackends() { await Task.WhenAll(RefreshModelsForRag(), RefreshModelsForStyle(), RefreshModelsForSpelling()); } private async Task SaveSettings() { if (_isAdmin) { await SettingsService.SaveOllamaUrlAsync(ollamaUrl); await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl); OllamaService.Reconfigure(ollamaUrl); await SettingsService.SaveOllamaCloudUrlAsync(_ollamaCloudUrl); await SettingsService.SaveOllamaCloudApiKeyAsync(_ollamaCloudApiKey); await SettingsService.SaveRagBackendAsync(ragBackend); await SettingsService.SaveStyleBackendAsync(styleBackend); await SettingsService.SaveSpellingBackendAsync(spellingBackend); if (!string.IsNullOrWhiteSpace(ragModel)) { var ok = await OllamaService.TestEmbeddingAsync(ragModel, ragBackend); if (!ok) { statusMessage = selectedLanguage == "en" ? $"Model '{ragModel}' does not support embeddings. RAG model not saved." : $"Modell '{ragModel}' unterstützt keine Embeddings. RAG-Modell nicht gespeichert."; //return; } } 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); await SettingsService.SaveAdvocatusDiaboliPromptAsync(advocatusDiaboliPrompt); await SettingsService.SaveHookFinderPromptAsync(hookFinderPrompt); await SettingsService.SaveEli5PromptAsync(eli5Prompt); await SettingsService.SaveCoAuthorBrainstormingPromptAsync(coAuthorPrompt); await SettingsService.SaveTechLastenheftPromptAsync(techLastenheftPrompt); await SettingsService.SaveTechPflichtenheftPromptAsync(techPflichtenheftPrompt); await SettingsService.SaveTechUseCasePromptAsync(techUseCasePrompt); await SettingsService.SaveTechStakeholderPromptAsync(techStakeholderPrompt); await SettingsService.SaveTechGlossarPromptAsync(techGlossarPrompt); await SettingsService.SaveTechQualitaetPromptAsync(techQualitaetPrompt); await SettingsService.SaveTechRisikoPromptAsync(techRisikoPrompt); await SettingsService.SaveLektorTempAsync(lektorTemp); await SettingsService.SaveFactCheckTempAsync(factCheckTemp); await SettingsService.SaveSpellingTempAsync(spellingTemp); await SettingsService.SaveAdvocatusDiaboliTempAsync(advocatusDiaboliTemp); await SettingsService.SaveHookFinderTempAsync(hookFinderTemp); await SettingsService.SaveEli5TempAsync(eli5Temp); await SettingsService.SaveCoAuthorTempAsync(coAuthorTemp); await SettingsService.SaveTechLastenheftTempAsync(techLastenheftTemp); await SettingsService.SaveTechPflichtenheftTempAsync(techPflichtenheftTemp); await SettingsService.SaveTechUseCaseTempAsync(techUseCaseTemp); await SettingsService.SaveTechStakeholderTempAsync(techStakeholderTemp); await SettingsService.SaveTechGlossarTempAsync(techGlossarTemp); await SettingsService.SaveTechQualitaetTempAsync(techQualitaetTemp); await SettingsService.SaveTechRisikoTempAsync(techRisikoTemp); if (selectedLanguage != Loc.CurrentLanguage) await Loc.SetLanguageAsync(selectedLanguage); statusMessage = Loc["settings.status.saved"]; await ThemeService.ApplyTheme(selectedTheme); } private async Task LoadPluginConfig() { var cfg = await PluginManager.GetConfigAsync("wordpress"); if (cfg != null) { if (cfg.Values.TryGetValue("url", out var url)) _wpUrl = url; if (cfg.Values.TryGetValue("username", out var user)) _wpUsername = user; if (cfg.Values.TryGetValue("password", out var pass)) _wpPassword = pass; } } private async Task SavePluginConfig() { var cfg = new PluginConfig { PluginId = "wordpress", Values = new() { ["url"] = _wpUrl ?? "", ["username"] = _wpUsername ?? "", ["password"] = _wpPassword ?? "" } }; await PluginManager.SaveConfigAsync(cfg); _pluginStatus = "Gespeichert."; } private string GetTempCategory(double temp) { if (temp <= 0.2) return Loc["settings.temperature.category.precise"]; if (temp <= 0.5) return Loc["settings.temperature.category.balanced"]; if (temp <= 1.2) return Loc["settings.temperature.category.creative"]; return Loc["settings.temperature.category.chaos"]; } private string GetTempDescription(double temp) { if (temp <= 0.2) return Loc["settings.temperature.desc.precise"]; if (temp <= 0.5) return Loc["settings.temperature.desc.balanced"]; if (temp <= 1.2) return Loc["settings.temperature.desc.creative"]; return Loc["settings.temperature.desc.chaos"]; } private string Fmt(double v) => v.ToString("F1", System.Globalization.CultureInfo.InvariantCulture); private void SetTemp(ChangeEventArgs e, Action setter) { if (e.Value is string s && double.TryParse(s, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var v)) setter(Math.Round(v, 1)); } private async Task TestConnection() { _isTestingConnection = true; _connectionTested = false; StateHasChanged(); OllamaService.Reconfigure(ollamaUrl); var result = await OllamaService.TestConnectionAsync(); _connectionOk = result.Success; _connectionLatency = result.LatencyMs; _connectionTested = true; _isTestingConnection = false; } private async Task RunAllDiagnostics() { _isDiagnosing = true; _diagnosticsResults = new(); StateHasChanged(); OllamaService.Reconfigure(ollamaUrl); var lang = selectedLanguage; var results = new List(); var conn = await OllamaService.TestConnectionAsync(); if (conn.Success) results.Add(lang == "en" ? $"✓ Connection: {conn.LatencyMs} ms" : $"✓ Verbindung: {conn.LatencyMs} ms"); else { results.Add(lang == "en" ? "✗ Connection failed" : "✗ Verbindung fehlgeschlagen"); _diagnosticsResults = results; _isDiagnosing = false; return; } var models = new[] { (Label: Loc["settings.models.label.style"] ?? "Style", Model: styleModel, Backend: styleBackend), (Label: Loc["settings.models.label.rag"] ?? "RAG", Model: ragModel, Backend: ragBackend), (Label: Loc["settings.models.label.spellcheck"] ?? "Spelling", Model: spellingModel, Backend: spellingBackend) }; foreach (var entry in models) { if (string.IsNullOrWhiteSpace(entry.Model)) { results.Add($"? {entry.Label}: {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not configured" : "nicht konfiguriert")}"); continue; } var exists = await OllamaService.ModelExistsAsync(entry.Model, entry.Backend); if (!exists) { results.Add($"✗ {entry.Label} ({entry.Model}): {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not found" : "nicht gefunden")}"); continue; } var latency = await OllamaService.MeasureLatencyAsync(entry.Model, entry.Backend); var latencyStr = latency >= 0 ? $"{latency} ms" : "—"; var ctx = await OllamaService.GetModelContextWindowAsync(entry.Model, entry.Backend); var ctxStr = ctx.Success ? $"{ctx.ContextWindow}" : "?"; results.Add($"✓ {entry.Label} ({entry.Model}): {latencyStr}, {(!string.IsNullOrEmpty(lang) && lang == "en" ? "Context" : "Kontext")}: {ctxStr}"); } _diagnosticsResults = results; _isDiagnosing = false; } }