@page "/editor" @page "/editor/{action}" @page "/editor/{action}/{projectId:int}" @inject IOllamaService OllamaService @inject IProjectService ProjectService @inject IRagService RagService @inject ISettingsService SettingsService @inject IWikipediaService WikipediaService @inject NavigationManager Navigation @implements IDisposable @rendermode InteractiveServer @attribute [Authorize] @T("Editor - Author Buddy", "Editor - Author Buddy")
@statusText @if (isChecking) { }
@if (showVersionDialog) { }
@searchQuery
@if (showSpellDiff && !string.IsNullOrEmpty(correctedText)) {

@T("Rechtschreibpr\u00fcfung - \u00c4nderungen:", "Spell check - Changes:")

} else if (ragResults != null && ragResults.Count > 0) {

@T("Gefundene Fakten (RAG):", "Found facts (RAG):")

@foreach (var r in ragResults) {
@T("\u00c4hnlichkeit:", "Similarity:") @r.Similarity.ToString("P1")
@r.FilePath
@r.Text
}
} else if (wikipediaResults != null && wikipediaResults.Count > 0) {

@T("Wikipedia-Recherche:", "Wikipedia research:")

@foreach (var r in wikipediaResults) {
@(r.Language == "de" ? "🇩🇪" : "🇬🇧") @r.Language.ToUpper()
@r.Snippet
@if (!string.IsNullOrEmpty(r.Extract)) {
@r.Extract
}
}
} else if (!string.IsNullOrEmpty(analysisResult)) {

@T("Ergebnis:", "Result:")

@analysisResult
}
@if (showCreateDialog) { } @code { private string _language = "de"; private string T(string de, string en) => _language == "en" ? en : de; [Parameter] public string? Action { get; set; } [Parameter] public int? ProjectId { get; set; } private string editorContent = string.Empty; private string searchQuery = string.Empty; private string statusText = string.Empty; private bool isChecking; private bool buttonDisabled; private double checkProgress; private string analysisResult = string.Empty; private string originalText = string.Empty; private string correctedText = string.Empty; private bool showSpellDiff; private List? ragResults; private List? wikipediaResults; private int currentProjectId; private string? currentFile; private bool showCreateDialog; private string newProjectName = string.Empty; private string newProjectPath = string.Empty; private Dictionary loadedArticles = new(); private bool showVersionDialog; private string versionComment = string.Empty; private VersionEntry? latestVersion; private record VersionEntry(string FilePath, DateTime Timestamp, string? Comment); protected override async Task OnInitializedAsync() { _language = await SettingsService.GetLanguageAsync() ?? "de"; statusText = T("Bereit", "Ready"); Navigation.LocationChanged += OnLocationChanged; await LoadProjectContext(); var pendingOutput = SettingsService.PendingAssistantOutput; if (!string.IsNullOrEmpty(pendingOutput)) { SettingsService.PendingAssistantOutput = null; editorContent = pendingOutput; statusText = T("Text aus Assistent \u00fcbernommen.", "Text imported from assistant."); } if (string.Equals(Action, "newproject", StringComparison.OrdinalIgnoreCase)) { showCreateDialog = true; } } private async void OnLocationChanged(object? sender, LocationChangedEventArgs e) { await LoadProjectContext(); StateHasChanged(); } public void Dispose() { Navigation.LocationChanged -= OnLocationChanged; } private void OpenAssistant() { SettingsService.PendingAssistantInput = editorContent; Navigation.NavigateTo("/assistant"); } private void HandleSearchInput(ChangeEventArgs e) { searchQuery = e.Value?.ToString() ?? string.Empty; buttonDisabled = isChecking || string.IsNullOrWhiteSpace(searchQuery); StateHasChanged(); } private async Task LoadProjectContext() { if (ProjectId.HasValue) { currentProjectId = ProjectId.Value; SettingsService.CurrentProjectId = currentProjectId; } else if (SettingsService.CurrentProjectId.HasValue) { currentProjectId = SettingsService.CurrentProjectId.Value; } if (currentProjectId > 0) await SettingsService.LoadProjectSettingsAsync(); var filePath = SettingsService.CurrentFilePath; if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath)) { currentFile = filePath; editorContent = await File.ReadAllTextAsync(filePath); statusText = $"{T("Geladen:", "Loaded:")} {Path.GetFileName(filePath)}"; RefreshLatestVersion(); } } private async Task CheckStyle() { if (string.IsNullOrWhiteSpace(editorContent)) return; isChecking = true; statusText = T("Ollama analysiert Stil...", "Ollama analyzing style..."); checkProgress = 50; showSpellDiff = false; ragResults = null; wikipediaResults = null; try { } catch (Exception ex) { analysisResult = $"{T("Fehler:", "Error:")} {ex.Message}"; } finally { statusText = T("Bereit", "Ready"); isChecking = false; checkProgress = 0; } } private async Task SpellCheck() { if (string.IsNullOrWhiteSpace(editorContent)) return; isChecking = true; statusText = T("Ollama pr\u00fcft Rechtschreibung...", "Ollama checking spelling..."); checkProgress = 50; showSpellDiff = false; ragResults = null; wikipediaResults = null; try { originalText = editorContent; correctedText = await OllamaService.QuickSpellCheckAsync(editorContent); showSpellDiff = true; } catch (Exception ex) { analysisResult = $"{T("Fehler:", "Error:")} {ex.Message}"; } finally { statusText = T("Bereit", "Ready"); isChecking = false; checkProgress = 0; } } private async Task RagSearch() { if (string.IsNullOrWhiteSpace(searchQuery)) return; isChecking = true; statusText = T("Suche verwandte Fakten...", "Searching related facts..."); showSpellDiff = false; analysisResult = string.Empty; ragResults = null; wikipediaResults = null; try { if (currentProjectId == 0) { analysisResult = T("Kein Projekt geladen. \u00d6ffne zuerst ein Projekt \u00fcber die Sidebar.", "No project loaded. Open a project via the sidebar first."); return; } ragResults = await RagService.SearchAsync(currentProjectId, searchQuery, 5); if (ragResults.Count == 0) { analysisResult = T("Keine relevanten Fakten gefunden. Indexiere das Projekt ggf. \u00fcber 'Projekt indexieren'.", "No relevant facts found. Index the project if needed via 'Index project'."); } } catch (Exception ex) { analysisResult = $"{T("RAG-Fehler:", "RAG error:")} {ex.Message}"; } finally { statusText = T("Bereit", "Ready"); isChecking = false; } } private async Task SearchWikipedia() { if (string.IsNullOrWhiteSpace(searchQuery)) return; isChecking = true; statusText = T("Suche auf Wikipedia...", "Searching Wikipedia..."); showSpellDiff = false; analysisResult = string.Empty; ragResults = null; wikipediaResults = null; loadedArticles.Clear(); try { var request = new WikipediaSearchRequest { Query = searchQuery, SearchGerman = true, SearchEnglish = true, MaxResults = 3 }; wikipediaResults = await WikipediaService.SearchAsync(request); foreach (var result in wikipediaResults) { var extract = await WikipediaService.GetExtractAsync(result.Title, result.Language); if (extract != null) result.Extract = extract.Length > 800 ? extract[..800] + "..." : extract; } if (wikipediaResults.Count == 0) analysisResult = T("Keine Wikipedia-Artikel gefunden.", "No Wikipedia articles found."); } catch (Exception ex) { analysisResult = $"{T("Wikipedia-Fehler:", "Wikipedia error:")} {ex.Message}"; } finally { statusText = T("Bereit", "Ready"); isChecking = false; } } private async Task LoadFullArticle(WikipediaResult result) { statusText = $"{T("Lade Artikel:", "Loading article:")} {result.Title}..."; try { var fullText = await WikipediaService.GetFullArticleAsync(result.Title, result.Language); if (fullText != null) { result.Extract = fullText; loadedArticles[result.Url] = true; statusText = T("Artikel geladen.", "Article loaded."); } else { statusText = T("Konnte Artikel nicht laden.", "Could not load article."); } } catch (Exception ex) { statusText = $"{T("Fehler:", "Error:")} {ex.Message}"; } } private async Task SaveAsFact(WikipediaResult result) { if (string.IsNullOrWhiteSpace(result.Extract) || currentProjectId == 0) return; statusText = T("Speichere als Fakt...", "Saving as fact..."); try { var projects = await ProjectService.GetAllProjectsAsync(); var project = projects.FirstOrDefault(p => p.Id == currentProjectId); if (project == null) { statusText = T("Projekt nicht gefunden.", "Project not found."); return; } var factsDir = Path.Combine(project.RootPath, "01_Fakten"); Directory.CreateDirectory(factsDir); var safeName = SanitizeFilename(searchQuery); var filePath = Path.Combine(factsDir, $"{safeName}.md"); var content = $"# {result.Title}\n" + $"- Quelle: [{result.Title}]({result.Url})\n" + $"- Sprache: {result.Language.ToUpper()}\n" + $"- Abgerufen: {DateTime.Now:dd.MM.yyyy HH:mm}\n\n" + result.Extract; await File.WriteAllTextAsync(filePath, content); statusText = $"{T("Gespeichert als:", "Saved as:")} {safeName}.md"; } catch (Exception ex) { statusText = $"{T("Fehler beim Speichern:", "Error saving:")} {ex.Message}"; } } private static string SanitizeFilename(string name) { var invalid = Path.GetInvalidFileNameChars(); var sanitized = new string(name.Where(c => !invalid.Contains(c)).ToArray()); return string.IsNullOrWhiteSpace(sanitized) ? "fakt" : sanitized.Trim(); } private void CancelCreateProject() { showCreateDialog = false; Navigation.NavigateTo("/editor"); } private async Task CreateProject() { if (string.IsNullOrWhiteSpace(newProjectName) || string.IsNullOrWhiteSpace(newProjectPath)) return; isChecking = true; statusText = T("Erstelle Projekt...", "Creating project..."); try { Directory.CreateDirectory(newProjectPath); var id = await ProjectService.CreateNewProjectAsync(newProjectName, newProjectPath); SettingsService.CurrentProjectId = id; await SettingsService.LoadProjectSettingsAsync(); showCreateDialog = false; Navigation.NavigateTo($"/editor/index/{id}"); } catch (Exception ex) { analysisResult = $"{T("Fehler beim Erstellen:", "Error creating:")} {ex.Message}"; } finally { isChecking = false; statusText = T("Bereit", "Ready"); } } private async Task IndexProject() { if (currentProjectId == 0) { analysisResult = T("Kein Projekt geladen. \u00d6ffne zuerst ein Projekt \u00fcber die Sidebar.", "No project loaded. Open a project via the sidebar first."); return; } isChecking = true; statusText = T("Indexiere Projekt...", "Indexing project..."); analysisResult = string.Empty; ragResults = null; try { var projects = await ProjectService.GetAllProjectsAsync(); var project = projects.FirstOrDefault(p => p.Id == currentProjectId); if (project == null) { analysisResult = T("Projekt nicht gefunden.", "Project not found."); return; } var p = new Progress(msg => { statusText = msg; InvokeAsync(StateHasChanged); }); await RagService.IndexProjectAsync(currentProjectId, project.RootPath, p); analysisResult = T("Projekt erfolgreich indexiert.", "Project indexed successfully."); } catch (Exception ex) { analysisResult = $"{T("Indexierungsfehler:", "Indexing error:")} {ex.Message}"; } finally { statusText = T("Bereit", "Ready"); isChecking = false; } } private async Task SaveCurrentFile() { if (string.IsNullOrEmpty(currentFile)) return; await File.WriteAllTextAsync(currentFile, editorContent); statusText = $"{T("Gespeichert:", "Saved:")} {Path.GetFileName(currentFile)}"; } private void ShowVersionDialog() { showVersionDialog = true; versionComment = string.Empty; } private async Task SaveVersioned() { if (string.IsNullOrEmpty(currentFile)) return; var dir = Path.GetDirectoryName(currentFile)!; var name = Path.GetFileNameWithoutExtension(currentFile); var ext = Path.GetExtension(currentFile); var versionsDir = Path.Combine(dir, "_versions"); Directory.CreateDirectory(versionsDir); var stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var versionPath = Path.Combine(versionsDir, $"{name}_{stamp}{ext}"); await File.WriteAllTextAsync(currentFile, editorContent); await File.WriteAllTextAsync(versionPath, editorContent); var meta = $"Kommentar: {versionComment}"; await File.WriteAllTextAsync(versionPath + ".meta", meta); statusText = $"{T("Version gespeichert:", "Version saved:")} {name}_{stamp}{ext}"; showVersionDialog = false; versionComment = string.Empty; RefreshLatestVersion(); } private void RefreshLatestVersion() { latestVersion = null; if (string.IsNullOrEmpty(currentFile)) return; var dir = Path.GetDirectoryName(currentFile)!; var name = Path.GetFileNameWithoutExtension(currentFile); var ext = Path.GetExtension(currentFile); var versionsDir = Path.Combine(dir, "_versions"); if (!Directory.Exists(versionsDir)) return; var pattern = $"{name}_*{ext}"; var files = Directory.GetFiles(versionsDir, pattern).OrderByDescending(f => f).ToList(); if (files.Count == 0) return; var latest = files.First(); var metaFile = latest + ".meta"; string? comment = null; if (File.Exists(metaFile)) { var lines = File.ReadAllLines(metaFile); if (lines.Length > 0 && lines[0].StartsWith("Kommentar: ")) comment = lines[0]["Kommentar: ".Length..]; } var fileName = Path.GetFileNameWithoutExtension(latest); var stamp = fileName[(name.Length + 1)..]; var parsed = DateTime.TryParseExact(stamp, "yyyyMMdd_HHmmss", null, System.Globalization.DateTimeStyles.None, out var dt); latestVersion = new VersionEntry( latest, parsed ? dt : File.GetLastWriteTime(latest), comment ); } private async Task LoadLatestVersion() { if (latestVersion == null) return; try { editorContent = await File.ReadAllTextAsync(latestVersion.FilePath); statusText = $"{T("Version vom", "Version from")} {latestVersion.Timestamp:dd.MM.yyyy HH:mm} {T("geladen.", "loaded.")}"; } catch (Exception ex) { statusText = $"{T("Fehler:", "Error:")} {ex.Message}"; } } }