734 lines
25 KiB
Text
734 lines
25 KiB
Text
@page "/quick-markdown"
|
||
@attribute [Authorize(Roles = "Administrator,Blogger")]
|
||
@inject IOllamaService OllamaService
|
||
@inject OllamaSettings Settings
|
||
@inject IJSRuntime JS
|
||
@inject IAuthService AuthService
|
||
@inject ISettingsService SettingsService
|
||
@inject IQuickMarkdownService QuickMarkdownService
|
||
@inject IPluginManager PluginManager
|
||
@inject NavigationManager Navigation
|
||
@inject IFactSearchToolService FactSearchToolService
|
||
|
||
<PageTitle>Quick Markdown</PageTitle>
|
||
|
||
<style>
|
||
.qm-fullscreen-textarea {
|
||
width: 100%;
|
||
height: calc(100vh - 140px);
|
||
font-size: 16px;
|
||
}
|
||
|
||
.qm-fullscreen-container {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 1000;
|
||
background: var(--bs-dark);
|
||
color: var(--bs-light);
|
||
}
|
||
|
||
.qm-fullscreen-container .qm-container {
|
||
max-width: 100%;
|
||
margin: 0;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.qm-fullscreen-container .qm-output-panel {
|
||
height: 40%;
|
||
border-top: 1px solid var(--bs-gray);
|
||
}
|
||
|
||
.qm-fullscreen-container .qm-input-panel {
|
||
height: 60%;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.qm-fullscreen-container .qm-prompt-selector,
|
||
.qm-fullscreen-container .qm-generate-row,
|
||
.qm-fullscreen-container .qm-range-labels {
|
||
display: none;
|
||
}
|
||
</style>
|
||
|
||
<div class="qm-container">
|
||
<h2>Quick Markdown</h2>
|
||
<p>Erzeugt aus einem Text und einem System-Prompt direkt formatiertes Markdown – ohne Projekt oder Speicherung.</p>
|
||
|
||
<div class="qm-layout">
|
||
<div class="qm-input-panel">
|
||
|
||
<div class="form-group">
|
||
<label>Prompt-Verwaltung</label>
|
||
<div class="qm-prompt-selector">
|
||
<select class="qm-prompt-select" value="@_selectedPromptId" @onchange="SwitchPrompt">
|
||
<option value="0">— Standard-Prompt —</option>
|
||
@foreach (var p in _savedPrompts)
|
||
{
|
||
<option value="@p.Id">@p.Name</option>
|
||
}
|
||
</select>
|
||
<button class="btn btn-sm @(_fullscreenMode ? "btn-primary" : "")" @onclick="ToggleFullscreen">Vollbild</button>
|
||
<button class="btn btn-sm" @onclick="ShowSavePromptDialog">+ Als Prompt speichern</button>
|
||
<button class="btn btn-sm" @onclick="DeleteCurrentPrompt" disabled="@(_selectedPromptId == 0)">Löschen</button>
|
||
</div>
|
||
@if (_showPromptNameInput)
|
||
{
|
||
<div class="qm-prompt-name-row">
|
||
<input @bind="_newPromptName" placeholder="Name für den Prompt…" />
|
||
<button class="btn btn-sm btn-primary" @onclick="ConfirmSavePrompt">Speichern</button>
|
||
<button class="btn btn-sm" @onclick="CancelSavePrompt">Abbrechen</button>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>System-Prompt</label>
|
||
<textarea class="qm-textarea qm-prompt @(_fullscreenMode ? "qm-fullscreen-textarea" : "")" @bind="_systemPrompt" @bind:after="PersistPromptDraft" rows="4"></textarea>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="tempRange">Temperatur: @_temperature.ToString("F2")</label>
|
||
<input id="tempRange" type="range" min="0" max="2" step="0.05" @bind="_temperature" @bind:event="oninput" @bind:after="PersistPromptDraft" />
|
||
<div class="qm-range-labels">
|
||
<span>präzise (0)</span>
|
||
<span>kreativ (2)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Text</label>
|
||
<textarea class="qm-textarea" @bind="_inputText" @bind:event="oninput" @bind:after="PersistTextDraft" rows="12" placeholder="Text hier eingeben…"></textarea>
|
||
</div>
|
||
|
||
<div class="qm-generate-row">
|
||
<button class="btn btn-primary" @onclick="Generate" disabled="@_generateDisabled">
|
||
@if (_isBusy)
|
||
{
|
||
<span>Generiere…</span>
|
||
}
|
||
else
|
||
{
|
||
<span>Markdown erzeugen</span>
|
||
}
|
||
</button>
|
||
<button class="btn btn-sm" @onclick="SaveEntry" disabled="@_saveEntryDisabled">
|
||
Eingabe & Ergebnis speichern
|
||
</button>
|
||
<button class="btn btn-sm" @onclick="StartFactSearch" disabled="@(_isBusy || string.IsNullOrWhiteSpace(_inputText))" title="Prüft die Zeile mit [Fakt] im Text per Websuche">
|
||
🔍 Fakten prüfen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="qm-output-panel">
|
||
<div class="form-group">
|
||
<label>Ausgabe</label>
|
||
<div class="qm-output-toolbar">
|
||
<button class="btn btn-small" @onclick="CopyOutput" disabled="@string.IsNullOrEmpty(_output)">📋 Kopieren</button>
|
||
<button class="btn btn-small" @onclick="ClearOutput">🗑 Leeren</button>
|
||
@if (_availablePlugins.Count > 0)
|
||
{
|
||
<select @bind="_selectedPluginId" style="flex:1;max-width:180px;font-size:12px;">
|
||
<option value="">Export via Plugin…</option>
|
||
@foreach (var p in _availablePlugins)
|
||
{
|
||
<option value="@p.Id">@p.Name</option>
|
||
}
|
||
</select>
|
||
<button class="btn btn-small btn-primary" @onclick="ExportViaPlugin" disabled="@(_selectedPluginId == "" || string.IsNullOrEmpty(_output) || _isExporting)">
|
||
@(_isExporting ? "Exportiere…" : "Export")
|
||
</button>
|
||
}
|
||
</div>
|
||
@if (!string.IsNullOrEmpty(_exportResult))
|
||
{
|
||
<div class="qm-notice" style="margin-top:4px;font-size:12px;">@_exportResult</div>
|
||
}
|
||
</div>
|
||
<textarea class="qm-textarea qm-output" @bind="_output" rows="30" readonly></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
@if (_savedEntries.Count > 0)
|
||
{
|
||
<div class="qm-saved-list">
|
||
<h4>Gespeicherte Texte</h4>
|
||
@foreach (var e in _savedEntries)
|
||
{
|
||
<div class="qm-saved-item" @onclick="() => LoadEntry(e.Id)">
|
||
<div class="qm-saved-meta">
|
||
<span class="qm-saved-date">@e.Timestamp.ToLocalTime().ToString("g")</span>
|
||
<span class="qm-saved-preview">@(e.InputText.Length > 60 ? e.InputText[..60] + "…" : e.InputText)</span>
|
||
</div>
|
||
<button class="btn btn-sm" @onclick="() => DeleteEntry(e.Id)" title="Löschen">🗑</button>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@if (!string.IsNullOrEmpty(_error))
|
||
{
|
||
<div class="auth-error">@_error</div>
|
||
}
|
||
|
||
@if (!string.IsNullOrEmpty(_modelNotice))
|
||
{
|
||
<div class="qm-notice">@_modelNotice</div>
|
||
}
|
||
|
||
@if (_showFactSearch)
|
||
{
|
||
<div class="qm-fact-drawer">
|
||
<div class="qm-fact-drawer-header">
|
||
<span>🔍 Fakten prüfen</span>
|
||
<button class="btn btn-sm" @onclick="CloseFactSearch">✕</button>
|
||
</div>
|
||
<div class="qm-fact-drawer-body">
|
||
<div class="form-group">
|
||
<label>Behauptung</label>
|
||
<div class="qm-fact-claim">@_factText</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="factQuery">Suchanfrage</label>
|
||
<div class="qm-fact-query-row">
|
||
<input id="factQuery" @bind="_searchQuery" @bind:event="oninput"
|
||
placeholder="Suchanfrage bearbeiten…" />
|
||
<button class="btn btn-sm btn-primary" @onclick="RunFactSearch"
|
||
disabled="@(_factSearching || string.IsNullOrWhiteSpace(_searchQuery))">
|
||
Suchen
|
||
</button>
|
||
</div>
|
||
<button class="btn btn-sm" @onclick="RegenerateFactQuery" disabled="@_factSearching">
|
||
Anfrage neu generieren
|
||
</button>
|
||
</div>
|
||
|
||
@if (_factSearching)
|
||
{
|
||
<div class="qm-fact-loading">Suche läuft…</div>
|
||
}
|
||
@if (!string.IsNullOrEmpty(_factSearchError))
|
||
{
|
||
<div class="qm-fact-error">@_factSearchError</div>
|
||
}
|
||
@if (_factResults.Count > 0)
|
||
{
|
||
<div class="qm-fact-results">
|
||
@foreach (var r in _factResults)
|
||
{
|
||
<div class="qm-fact-result">
|
||
<a href="@r.Url" target="_blank" rel="noopener noreferrer">@r.Title</a>
|
||
@if (!string.IsNullOrEmpty(r.Snippet))
|
||
{
|
||
<div class="qm-fact-snippet">@r.Snippet</div>
|
||
}
|
||
<div class="qm-fact-result-actions">
|
||
<button class="btn btn-sm btn-primary" @onclick="() => SelectFactResult(r)"
|
||
disabled="@_factSummarizing">
|
||
@(_factSummarizing ? "Fasse zusammen…" : "Übernehmen")
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
else if (!_factSearching && _factSearchSearched && _factSearchError.Length == 0)
|
||
{
|
||
<div class="qm-fact-hint">Keine Ergebnisse gefunden. Versuche die Suchanfrage anzupassen.</div>
|
||
}
|
||
else if (!_factSearching && !_factSearchSearched)
|
||
{
|
||
<div class="qm-fact-hint">Klicke auf „Suchen", um Ergebnisse zu laden.</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
@code {
|
||
private const string DraftKeyText = "qm_draft_text";
|
||
private const string DraftKeyPrompt = "qm_draft_prompt";
|
||
private const string DraftKeyTemp = "qm_draft_temp";
|
||
private const string DraftKeyPromptId = "qm_draft_prompt_id";
|
||
|
||
private bool _fullscreenMode;
|
||
private string _systemPrompt = string.Empty;
|
||
private string _inputText = string.Empty;
|
||
private string _output = string.Empty;
|
||
private string _error = string.Empty;
|
||
private string _modelNotice = string.Empty;
|
||
private bool _isBusy;
|
||
private bool _generateDisabled => _isBusy || string.IsNullOrWhiteSpace(_inputText);
|
||
private bool _saveEntryDisabled => string.IsNullOrWhiteSpace(_inputText) && string.IsNullOrWhiteSpace(_output);
|
||
private double _temperature = 0.3;
|
||
private int _userId;
|
||
private List<QuickMarkdownEntry> _savedEntries = new();
|
||
private List<QuickMarkdownPrompt> _savedPrompts = new();
|
||
private int _selectedPromptId;
|
||
private bool _showPromptNameInput;
|
||
private string _newPromptName = string.Empty;
|
||
private List<IExportPlugin> _availablePlugins = new();
|
||
private string _selectedPluginId = string.Empty;
|
||
private bool _isExporting;
|
||
private string _exportResult = string.Empty;
|
||
private bool _draftRestored;
|
||
private bool _showFactSearch;
|
||
private bool _factSearching;
|
||
private bool _factSearchSearched;
|
||
private bool _factSummarizing;
|
||
private string _factSearchError = string.Empty;
|
||
private string _factLine = string.Empty;
|
||
private string _factText = string.Empty;
|
||
private string _searchQuery = string.Empty;
|
||
private List<FactSearchResult> _factResults = new();
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
var user = await AuthService.GetCurrentUserAsync();
|
||
if (user == null || !user.HasAnyRole(UserRole.Administrator, UserRole.Blogger))
|
||
{
|
||
Navigation.NavigateTo("/");
|
||
return;
|
||
}
|
||
_userId = user.Id;
|
||
|
||
_savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId);
|
||
_systemPrompt = await SettingsService.GetQuickMarkdownPromptAsync()
|
||
?? Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
|
||
_temperature = await SettingsService.GetQuickMarkdownTempAsync();
|
||
_savedEntries = await QuickMarkdownService.GetAllAsync(_userId);
|
||
_availablePlugins = PluginManager.GetPlugins().ToList();
|
||
|
||
Navigation.LocationChanged += OnLocationChanged;
|
||
|
||
_modelNotice = $"Verwendetes Modell: {Settings.StyleModel} (via {Settings.Backend})";
|
||
}
|
||
|
||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||
{
|
||
if (firstRender && !_draftRestored)
|
||
{
|
||
_draftRestored = true;
|
||
await RestoreDraftAsync();
|
||
await InvokeAsync(StateHasChanged);
|
||
}
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
Navigation.LocationChanged -= OnLocationChanged;
|
||
}
|
||
|
||
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
||
{
|
||
_ = PersistDraftAsync();
|
||
}
|
||
|
||
private async Task PersistTextDraft()
|
||
{
|
||
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyText, _inputText ?? string.Empty);
|
||
}
|
||
|
||
private async Task PersistPromptDraft()
|
||
{
|
||
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyPrompt, _systemPrompt ?? string.Empty);
|
||
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyTemp, _temperature.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyPromptId, _selectedPromptId.ToString());
|
||
}
|
||
|
||
private async Task PersistDraftAsync()
|
||
{
|
||
await PersistTextDraft();
|
||
await PersistPromptDraft();
|
||
}
|
||
|
||
private async Task RestoreDraftAsync()
|
||
{
|
||
var text = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyText);
|
||
if (!string.IsNullOrEmpty(text))
|
||
_inputText = text;
|
||
|
||
var promptIdStr = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyPromptId);
|
||
if (int.TryParse(promptIdStr, out var promptId) && _savedPrompts.Any(p => p.Id == promptId))
|
||
{
|
||
_selectedPromptId = promptId;
|
||
var prompt = _savedPrompts.First(p => p.Id == promptId);
|
||
_systemPrompt = prompt.PromptText;
|
||
_temperature = prompt.Temperature;
|
||
}
|
||
else
|
||
{
|
||
var prompt = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyPrompt);
|
||
if (!string.IsNullOrEmpty(prompt))
|
||
_systemPrompt = prompt;
|
||
|
||
var tempStr = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyTemp);
|
||
if (double.TryParse(tempStr, System.Globalization.CultureInfo.InvariantCulture, out var temp))
|
||
_temperature = temp;
|
||
}
|
||
}
|
||
|
||
private async Task SwitchPrompt(ChangeEventArgs e)
|
||
{
|
||
var id = int.Parse(e.Value?.ToString() ?? "0");
|
||
_selectedPromptId = id;
|
||
if (id == 0)
|
||
{
|
||
_systemPrompt = Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
|
||
_temperature = Settings.QuickMarkdownTemp;
|
||
}
|
||
else
|
||
{
|
||
var prompt = _savedPrompts.FirstOrDefault(p => p.Id == id);
|
||
if (prompt != null)
|
||
{
|
||
_systemPrompt = prompt.PromptText;
|
||
_temperature = prompt.Temperature;
|
||
}
|
||
}
|
||
|
||
await PersistPromptDraft();
|
||
}
|
||
|
||
private void ShowSavePromptDialog()
|
||
{
|
||
_newPromptName = string.Empty;
|
||
_showPromptNameInput = true;
|
||
}
|
||
|
||
private async Task ConfirmSavePrompt()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_newPromptName))
|
||
return;
|
||
|
||
if (_fullscreenMode && _selectedPromptId > 0)
|
||
{
|
||
var prompt = _savedPrompts.FirstOrDefault(p => p.Id == _selectedPromptId);
|
||
if (prompt != null)
|
||
{
|
||
prompt.PromptText = _systemPrompt;
|
||
prompt.Temperature = _temperature;
|
||
prompt.Timestamp = DateTime.UtcNow;
|
||
await QuickMarkdownService.SavePromptAsync(prompt);
|
||
_selectedPromptId = prompt.Id;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var existing = _savedPrompts.FirstOrDefault(p =>
|
||
p.Name == _newPromptName && p.Id != _selectedPromptId);
|
||
if (existing != null)
|
||
{
|
||
existing.PromptText = _systemPrompt;
|
||
existing.Temperature = _temperature;
|
||
existing.Timestamp = DateTime.UtcNow;
|
||
await QuickMarkdownService.SavePromptAsync(existing);
|
||
_selectedPromptId = existing.Id;
|
||
}
|
||
else
|
||
{
|
||
var prompt = new QuickMarkdownPrompt
|
||
{
|
||
UserId = _userId,
|
||
Name = _newPromptName,
|
||
PromptText = _systemPrompt,
|
||
Temperature = _temperature,
|
||
Timestamp = DateTime.UtcNow
|
||
};
|
||
await QuickMarkdownService.SavePromptAsync(prompt);
|
||
_selectedPromptId = prompt.Id;
|
||
}
|
||
}
|
||
|
||
_savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId);
|
||
_showPromptNameInput = false;
|
||
_newPromptName = string.Empty;
|
||
|
||
await PersistPromptDraft();
|
||
}
|
||
|
||
private void CancelSavePrompt()
|
||
{
|
||
_showPromptNameInput = false;
|
||
_newPromptName = string.Empty;
|
||
}
|
||
|
||
private void ToggleFullscreen()
|
||
{
|
||
_fullscreenMode = !_fullscreenMode;
|
||
}
|
||
|
||
private async Task DeleteCurrentPrompt()
|
||
{
|
||
if (_selectedPromptId == 0)
|
||
return;
|
||
|
||
await QuickMarkdownService.DeletePromptAsync(_selectedPromptId);
|
||
_savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId);
|
||
_selectedPromptId = 0;
|
||
_systemPrompt = Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
|
||
_temperature = Settings.QuickMarkdownTemp;
|
||
|
||
await PersistPromptDraft();
|
||
}
|
||
|
||
private async Task SaveEntry()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_inputText) && string.IsNullOrWhiteSpace(_output))
|
||
return;
|
||
|
||
var entry = new QuickMarkdownEntry
|
||
{
|
||
UserId = _userId,
|
||
InputText = _inputText,
|
||
OutputText = _output,
|
||
SystemPrompt = _systemPrompt,
|
||
Temperature = _temperature,
|
||
Timestamp = DateTime.UtcNow
|
||
};
|
||
await QuickMarkdownService.SaveAsync(entry);
|
||
_savedEntries = await QuickMarkdownService.GetAllAsync(_userId);
|
||
}
|
||
|
||
private async Task LoadEntry(int id)
|
||
{
|
||
var entry = await QuickMarkdownService.GetByIdAsync(id);
|
||
if (entry == null) return;
|
||
|
||
_inputText = entry.InputText;
|
||
_output = entry.OutputText;
|
||
|
||
if (!string.IsNullOrEmpty(entry.SystemPrompt))
|
||
{
|
||
_systemPrompt = entry.SystemPrompt;
|
||
_temperature = entry.Temperature;
|
||
}
|
||
|
||
await PersistDraftAsync();
|
||
}
|
||
|
||
private async Task DeleteEntry(int id)
|
||
{
|
||
await QuickMarkdownService.DeleteAsync(id);
|
||
_savedEntries = await QuickMarkdownService.GetAllAsync(_userId);
|
||
}
|
||
|
||
private async Task Generate()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_inputText)) return;
|
||
|
||
await PersistDraftAsync();
|
||
|
||
_isBusy = true;
|
||
_error = string.Empty;
|
||
_output = string.Empty;
|
||
|
||
try
|
||
{
|
||
var result = await OllamaService.Chat(Settings.StyleModel, _inputText, _systemPrompt, _temperature, Settings.StyleBackend);
|
||
_output = result;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_error = $"Fehler bei der Generierung: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_isBusy = false;
|
||
}
|
||
}
|
||
|
||
private async Task CopyOutput()
|
||
{
|
||
if (string.IsNullOrEmpty(_output)) return;
|
||
await JS.InvokeVoidAsync("navigator.clipboard.writeText", _output);
|
||
}
|
||
|
||
private void ClearOutput()
|
||
{
|
||
_output = string.Empty;
|
||
_error = string.Empty;
|
||
}
|
||
|
||
private async Task StartFactSearch()
|
||
{
|
||
if (_isBusy || string.IsNullOrWhiteSpace(_inputText)) return;
|
||
|
||
var found = FindFirstFact(_inputText);
|
||
if (found == null)
|
||
{
|
||
_error = "Keine Zeile mit [Fakt] gefunden. Schreibe in eine Zeile z.B.: [Fakt] Paris ist die Hauptstadt von Frankreich.";
|
||
return;
|
||
}
|
||
|
||
_error = string.Empty;
|
||
_factLine = found.Value.Line;
|
||
_factText = found.Value.Fact;
|
||
_showFactSearch = true;
|
||
_factResults = new();
|
||
_factSearchError = string.Empty;
|
||
_factSearchSearched = false;
|
||
|
||
_factSearching = true;
|
||
await InvokeAsync(StateHasChanged);
|
||
try
|
||
{
|
||
_searchQuery = await FactSearchToolService.CreateSearchQueryAsync(_factText);
|
||
await SearchCoreAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_factSearchError = $"Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_factSearching = false;
|
||
}
|
||
}
|
||
|
||
private async Task RunFactSearch()
|
||
{
|
||
if (_factSearching || string.IsNullOrWhiteSpace(_searchQuery)) return;
|
||
|
||
_factSearching = true;
|
||
_factSearchError = string.Empty;
|
||
_factSearchSearched = false;
|
||
await InvokeAsync(StateHasChanged);
|
||
try
|
||
{
|
||
await SearchCoreAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_factSearchError = $"Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_factSearching = false;
|
||
}
|
||
}
|
||
|
||
private async Task RegenerateFactQuery()
|
||
{
|
||
if (_factSearching) return;
|
||
|
||
_factSearching = true;
|
||
_factSearchError = string.Empty;
|
||
await InvokeAsync(StateHasChanged);
|
||
try
|
||
{
|
||
_searchQuery = await FactSearchToolService.CreateSearchQueryAsync(_factText);
|
||
_factSearchSearched = false;
|
||
await SearchCoreAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_factSearchError = $"Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_factSearching = false;
|
||
}
|
||
}
|
||
|
||
private async Task SearchCoreAsync()
|
||
{
|
||
_factResults = await FactSearchToolService.SearchAsync(_searchQuery);
|
||
_factSearchSearched = true;
|
||
}
|
||
|
||
private async Task SelectFactResult(FactSearchResult result)
|
||
{
|
||
if (_factSummarizing) return;
|
||
|
||
_factSummarizing = true;
|
||
_factSearchError = string.Empty;
|
||
await InvokeAsync(StateHasChanged);
|
||
try
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(result.Url))
|
||
await JS.InvokeVoidAsync("window.open", result.Url, "_blank");
|
||
|
||
var info = string.IsNullOrWhiteSpace(result.Snippet) ? result.Title : result.Snippet;
|
||
var summary = await FactSearchToolService.SummarizeAsync(_searchQuery, info, result.Url);
|
||
|
||
var replacement = string.IsNullOrWhiteSpace(summary)
|
||
? $"({result.Title}, Quelle: {result.Url})"
|
||
: $"{summary} (Quelle: {result.Url})";
|
||
|
||
_inputText = _inputText.Replace(_factLine, replacement);
|
||
await PersistDraftAsync();
|
||
_showFactSearch = false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_factSearchError = $"Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_factSummarizing = false;
|
||
}
|
||
}
|
||
|
||
private void CloseFactSearch()
|
||
{
|
||
_showFactSearch = false;
|
||
}
|
||
|
||
private static (string Line, string Fact)? FindFirstFact(string text)
|
||
{
|
||
if (string.IsNullOrEmpty(text)) return null;
|
||
|
||
foreach (var line in text.Split('\n'))
|
||
{
|
||
var idx = line.IndexOf("[Fakt]", StringComparison.OrdinalIgnoreCase);
|
||
if (idx < 0) continue;
|
||
|
||
var fact = line.Remove(idx, "[Fakt]".Length);
|
||
fact = fact.Trim().TrimEnd('.', '!', '?', ':', ';', ',', '-').Trim();
|
||
|
||
if (!string.IsNullOrWhiteSpace(fact))
|
||
return (line, fact);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private async Task ExportViaPlugin()
|
||
{
|
||
if (string.IsNullOrEmpty(_selectedPluginId) || string.IsNullOrEmpty(_output)) return;
|
||
|
||
var plugin = PluginManager.GetPlugin(_selectedPluginId);
|
||
if (plugin == null) return;
|
||
|
||
_isExporting = true;
|
||
_exportResult = string.Empty;
|
||
|
||
try
|
||
{
|
||
var title = _inputText.Length > 80 ? _inputText[..80] + "…" : _inputText;
|
||
title = string.IsNullOrWhiteSpace(title) ? "Quick Markdown Export" : title;
|
||
|
||
var context = new ExportPluginContext
|
||
{
|
||
Title = title,
|
||
Content = _output
|
||
};
|
||
|
||
var result = await plugin.ExportAsync(context);
|
||
_exportResult = result.Message;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_exportResult = $"Fehler: {ex.Message}";
|
||
}
|
||
finally
|
||
{
|
||
_isExporting = false;
|
||
}
|
||
}
|
||
}
|