@page "/settings" @attribute [Authorize] @rendermode InteractiveServer @inject ISettingsService SettingsService @inject IThemeService ThemeService @inject IOllamaService OllamaService @inject NavigationManager Navigation @inject IJSRuntime JSRuntime @inject IProjectService ProjectService @inject ILocalizationService Loc @inject IAuthService AuthService @Loc["page.title.settings"] - Author Buddy
@if (_isAdmin) {

@Loc["settings.section.user"]

}
@if (currentProjectName != null) { @Loc["settings.context.project"]: @currentProjectName } else { @Loc["settings.context.user"] }
@if (_isAdmin) {

@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

@Loc["settings.section.diagnostics"]

@if (_diagnosticsResults != null) {
@foreach (var line in _diagnosticsResults) {
@line
}
}
}

@Loc["settings.section.appearance"]

@Loc["settings.section.language"]

@if (currentProjectTemplate == null || currentProjectTemplate == ProjectTemplate.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)
} @if (currentProjectTemplate == null || currentProjectTemplate == ProjectTemplate.Novel) {

@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 (currentProjectTemplate == null || currentProjectTemplate == ProjectTemplate.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 ragModel = "llama3.2"; private string styleModel = "llama3.2"; private string spellingModel = "phi3:mini"; private string selectedTheme = "light"; private string ollamaUrl = "http://localhost:11434"; private string llamaCppUrl = "http://localhost:8080"; private string selectedBackend = "Ollama"; 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 availableModels = 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; protected override async Task OnInitializedAsync() { var currentUser = AuthService.CurrentUser; _isAdmin = currentUser?.Role == UserRole.Administrator; 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) { try { var models = await OllamaService.GetLocalModelsAsync(); availableModels = models.ToList(); } catch { availableModels = new(); } } } private async Task LoadUserSettings() { 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"; llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080"; selectedBackend = await SettingsService.GetBackendAsync() ?? "Ollama"; 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; 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"; } 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 SaveSettings() { if (_isAdmin) { await SettingsService.SaveOllamaUrlAsync(ollamaUrl); await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl); await SettingsService.SaveBackendAsync(selectedBackend); OllamaService.Reconfigure(ollamaUrl); if (!string.IsNullOrWhiteSpace(ragModel)) { var ok = await OllamaService.TestEmbeddingAsync(ragModel); 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); await SettingsService.SaveLanguageAsync(selectedLanguage); if (selectedLanguage != Loc.CurrentLanguage) await Loc.SetLanguageAsync(selectedLanguage); statusMessage = Loc["settings.status.saved"]; await ThemeService.ApplyTheme(selectedTheme); } 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[] { (Loc["settings.models.label.style"] ?? "Style", styleModel), (Loc["settings.models.label.rag"] ?? "RAG", ragModel), (Loc["settings.models.label.spellcheck"] ?? "Spelling", spellingModel) }; foreach (var (label, model) in models) { if (string.IsNullOrWhiteSpace(model)) { results.Add($"? {label}: {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not configured" : "nicht konfiguriert")}"); continue; } var exists = await OllamaService.ModelExistsAsync(model); if (!exists) { results.Add($"✗ {label} ({model}): {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not found" : "nicht gefunden")}"); continue; } var latency = await OllamaService.MeasureLatencyAsync(model); var latencyStr = latency >= 0 ? $"{latency} ms" : "—"; var ctx = await OllamaService.GetModelContextWindowAsync(model); var ctxStr = ctx.Success ? $"{ctx.ContextWindow}" : "?"; results.Add($"✓ {label} ({model}): {latencyStr}, {(!string.IsNullOrEmpty(lang) && lang == "en" ? "Context" : "Kontext")}: {ctxStr}"); } _diagnosticsResults = results; _isDiagnosing = false; } }