@page "/assistant" @attribute [Authorize] @inject IOllamaService OllamaService @inject ISettingsService SettingsService @inject IRagService RagService @inject IProjectService ProjectService @inject NavigationManager Navigation @inject ILocalizationService Loc @inject OperationState OpState @Loc["page.title.assistant"]
@if (!string.IsNullOrEmpty(outputText)) { } @statusText @if (isRunning) { }
@if (showPromptEditor && selectedMode != "rag") {
@Loc["assistant.label.system_prompt"]
}

@Loc["assistant.label.input_text"]

@Loc["assistant.label.result"]

@if (!string.IsNullOrEmpty(outputText)) {
@outputText
} else if (!string.IsNullOrEmpty(errorMessage)) {
@errorMessage
} else { @Loc["assistant.hint.empty_result"] }
@code { private string selectedMode = "lektorat"; private string inputText = string.Empty; private string outputText = string.Empty; private string errorMessage = string.Empty; private string statusText = string.Empty; private bool isRunning; private double progress; private bool showPromptEditor; private string promptOverride = string.Empty; private double temperatureOverride = 0.7; private ProjectTemplate currentTemplate = ProjectTemplate.Novel; protected override async Task OnInitializedAsync() { statusText = Loc["common.status.ready"]; await LoadPromptForMode(); var pid = SettingsService.CurrentProjectId; if (pid != null) { var project = await ProjectService.GetProjectAsync(pid.Value); if (project != null) currentTemplate = project.Template; } var pending = SettingsService.PendingAssistantInput; if (!string.IsNullOrEmpty(pending)) { inputText = pending; SettingsService.PendingAssistantInput = null; } } private async Task OnModeChanged(ChangeEventArgs e) { selectedMode = e.Value?.ToString() ?? "lektorat"; await LoadPromptForMode(); } private async Task LoadPromptForMode() { if (selectedMode == "rag") { promptOverride = string.Empty; temperatureOverride = 0.3; return; } var (prompt, temp) = selectedMode switch { "lektorat" => (await SettingsService.GetLektorSystemPromptAsync() ?? string.Empty, await SettingsService.GetLektorTempAsync()), "advocatus" => (await SettingsService.GetAdvocatusDiaboliPromptAsync() ?? string.Empty, await SettingsService.GetAdvocatusDiaboliTempAsync()), "hookfinder" => (await SettingsService.GetHookFinderPromptAsync() ?? string.Empty, await SettingsService.GetHookFinderTempAsync()), "eli5" => (await SettingsService.GetEli5PromptAsync() ?? string.Empty, await SettingsService.GetEli5TempAsync()), "coauthor" => (await SettingsService.GetCoAuthorBrainstormingPromptAsync() ?? string.Empty, await SettingsService.GetCoAuthorTempAsync()), "tech_lastenheft" => (await SettingsService.GetTechLastenheftPromptAsync() ?? string.Empty, await SettingsService.GetTechLastenheftTempAsync()), "tech_pflichtenheft" => (await SettingsService.GetTechPflichtenheftPromptAsync() ?? string.Empty, await SettingsService.GetTechPflichtenheftTempAsync()), "tech_usecase" => (await SettingsService.GetTechUseCasePromptAsync() ?? string.Empty, await SettingsService.GetTechUseCaseTempAsync()), "tech_stakeholder" => (await SettingsService.GetTechStakeholderPromptAsync() ?? string.Empty, await SettingsService.GetTechStakeholderTempAsync()), "tech_glossar" => (await SettingsService.GetTechGlossarPromptAsync() ?? string.Empty, await SettingsService.GetTechGlossarTempAsync()), "tech_qualitaet" => (await SettingsService.GetTechQualitaetPromptAsync() ?? string.Empty, await SettingsService.GetTechQualitaetTempAsync()), "tech_risiko" => (await SettingsService.GetTechRisikoPromptAsync() ?? string.Empty, await SettingsService.GetTechRisikoTempAsync()), _ => (string.Empty, 0.0) }; promptOverride = prompt; temperatureOverride = temp; } private async Task ResetPrompt() { await LoadPromptForMode(); } private void OnTempChanged(ChangeEventArgs e) { if (e.Value is string s && double.TryParse(s, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var v)) temperatureOverride = Math.Round(v, 1); } private string Fmt(double v) => v.ToString("F1", System.Globalization.CultureInfo.InvariantCulture); private async Task RunAssistant() { if (string.IsNullOrWhiteSpace(inputText)) return; isRunning = true; progress = 50; outputText = string.Empty; errorMessage = string.Empty; statusText = Loc["assistant.status.starting"]; OpState.SetStatus(Loc["assistant.status.starting"], true); OpState.SetThought(""); try { if (selectedMode == "rag") { var projectId = SettingsService.CurrentProjectId; if (projectId == null) { errorMessage = Loc["assistant.error.no_project"]; return; } OpState.SetStatus(Loc["assistant.status.searching"], true); progress = 30; var results = await RagService.SearchAsync(projectId.Value, inputText, 5); if (results.Count == 0) { errorMessage = Loc["assistant.error.no_facts"]; return; } var facts = string.Join("\n\n---\n\n", results.Select(r => r.Text)); progress = 70; OpState.SetStatus(Loc["assistant.status.processing"], true); outputText = await OllamaService.AnswerBasedOnFactsAsync(inputText, facts); OpState.SetThought(outputText); statusText = Loc["assistant.status.done"]; OpState.SetStatus(Loc["assistant.status.done"], false); } else { var systemPrompt = promptOverride; if (string.IsNullOrEmpty(systemPrompt)) { errorMessage = Loc["assistant.error.no_prompt"]; return; } if (selectedMode is "coauthor" or "hookfinder" or "lektorat") { var projectId = SettingsService.CurrentProjectId; if (projectId != null) { var project = await ProjectService.GetProjectAsync(projectId.Value); if (project != null) { // Use Exposé file from category system var categories = await ProjectService.GetFileCategoriesAsync(projectId.Value); var exposeFile = categories.FirstOrDefault(c => c.Value == FileCategory.Exposee); if (!string.IsNullOrEmpty(exposeFile.Key) && File.Exists(exposeFile.Key)) { var exposee = await File.ReadAllTextAsync(exposeFile.Key); if (!string.IsNullOrWhiteSpace(exposee)) { systemPrompt += "\n\n" + Loc["assistant.exposee.context"] + "\n" + exposee; } } } } } var model = await SettingsService.GetStyleModelAsync() ?? "llama3.2"; OpState.SetStatus(Loc["assistant.status.processing"], true); statusText = Loc["assistant.status.processing"]; await OllamaService.ChatStream(model, inputText, async chunk => { outputText += chunk; OpState.AppendThought(chunk); await InvokeAsync(StateHasChanged); }, systemPrompt, temperatureOverride); statusText = Loc["assistant.status.done"]; OpState.SetStatus(Loc["assistant.status.done"], false); } } catch (Exception ex) { errorMessage = $"{Loc["common.error"]} {ex.Message}"; statusText = Loc["common.status.error"]; OpState.SetStatus($"{Loc["common.error"]} {ex.Message}", false); } finally { isRunning = false; progress = 0; } } private void SendToEditor() { SettingsService.PendingAssistantOutput = outputText; Navigation.NavigateTo("/editor"); } }