@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 (showPromptEditor && selectedMode != "rag")
{
}
@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");
}
}