2026-05-14 12:31:35 +02:00
|
|
|
@page "/settings"
|
2026-05-21 11:04:42 +02:00
|
|
|
@attribute [Authorize]
|
2026-05-31 19:31:27 +02:00
|
|
|
@rendermode InteractiveServer
|
2026-05-16 16:13:35 +02:00
|
|
|
@inject ISettingsService SettingsService
|
|
|
|
|
@inject IThemeService ThemeService
|
2026-05-14 12:31:35 +02:00
|
|
|
@inject IOllamaService OllamaService
|
|
|
|
|
@inject NavigationManager Navigation
|
|
|
|
|
@inject IJSRuntime JSRuntime
|
|
|
|
|
@inject IProjectService ProjectService
|
2026-05-25 06:38:16 +02:00
|
|
|
@inject ILocalizationService Loc
|
2026-05-31 19:31:27 +02:00
|
|
|
@inject IAuthService AuthService
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-05-25 06:38:16 +02:00
|
|
|
<PageTitle>@Loc["page.title.settings"] - Author Buddy</PageTitle>
|
2026-05-14 12:31:35 +02:00
|
|
|
|
|
|
|
|
<div class="settings-page">
|
|
|
|
|
|
2026-05-25 06:38:16 +02:00
|
|
|
<div class="settings-context">
|
|
|
|
|
@if (currentProjectName != null)
|
|
|
|
|
{
|
|
|
|
|
<span class="settings-badge settings-badge-project">@Loc["settings.context.project"]: @currentProjectName</span>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
<span class="settings-badge settings-badge-user">@Loc["settings.context.user"]</span>
|
|
|
|
|
}
|
2026-05-21 11:04:42 +02:00
|
|
|
</div>
|
|
|
|
|
|
2026-06-21 07:52:03 +02:00
|
|
|
<div class="settings-tabs">
|
2026-07-04 10:32:35 +02:00
|
|
|
@if (_isAdmin)
|
|
|
|
|
{
|
|
|
|
|
<button class="settings-tab @(_activePromptTab == "admin" ? "active" : "")" @onclick="SwitchToAdminTab">
|
|
|
|
|
Admin
|
|
|
|
|
</button>
|
|
|
|
|
}
|
2026-06-21 07:52:03 +02:00
|
|
|
<button class="settings-tab @(_activePromptTab == "novel" ? "active" : "")" @onclick="SwitchToNovelTab">
|
|
|
|
|
Roman
|
|
|
|
|
</button>
|
|
|
|
|
<button class="settings-tab @(_activePromptTab == "technical" ? "active" : "")" @onclick="SwitchToTechnicalTab">
|
|
|
|
|
Technische Vorlage (Requirements)
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-16 16:13:35 +02:00
|
|
|
<div class="settings-layout">
|
2026-07-04 10:32:35 +02:00
|
|
|
@if (_isAdmin && _activePromptTab == "admin")
|
|
|
|
|
{
|
|
|
|
|
<div class="settings-two-column">
|
2026-05-16 16:13:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-column">
|
|
|
|
|
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.user"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="userSelect">@Loc["settings.user.label.active"]</label>
|
|
|
|
|
<select id="userSelect" value="@selectedUserId" @onchange="SwitchUser">
|
|
|
|
|
@foreach (var u in users)
|
|
|
|
|
{
|
|
|
|
|
<option value="@u.Id">@u.Name (@(u.IsAdministrator ? Loc["settings.user.role.admin"] : Loc["settings.user.role.author"]))</option>
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="newUserName">@Loc["settings.user.label.new"]</label>
|
|
|
|
|
<div style="display: flex; gap: 8px;">
|
|
|
|
|
<input id="newUserName" @bind="newUserName" placeholder="@Loc["settings.user.placeholder.name"]" style="flex: 1;" />
|
|
|
|
|
<button class="btn btn-secondary" @onclick="AddUser">@Loc["settings.user.button.add"]</button>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section" style="margin-bottom:16px;">
|
|
|
|
|
<button class="btn btn-secondary" @onclick='() => Navigation.NavigateTo("/setup-2fa")'>@Loc["settings.user.link.2fa"]</button>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-07-04 10:32:35 +02:00
|
|
|
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.models"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="ragModel">@Loc["settings.models.label.rag"]</label>
|
|
|
|
|
<select id="ragModel" @bind="ragModel">
|
|
|
|
|
@if (availableModels.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
<option value="">@Loc["settings.models.empty"]</option>
|
|
|
|
|
}
|
|
|
|
|
@foreach (var m in availableModels)
|
|
|
|
|
{
|
|
|
|
|
<option value="@m.Name">@m.Name</option>
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="styleModel">@Loc["settings.models.label.style"]</label>
|
|
|
|
|
<select id="styleModel" @bind="styleModel">
|
|
|
|
|
@if (availableModels.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
<option value="">@Loc["settings.models.empty"]</option>
|
|
|
|
|
}
|
|
|
|
|
@foreach (var m in availableModels)
|
|
|
|
|
{
|
|
|
|
|
<option value="@m.Name">@m.Name</option>
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="spellingModel">@Loc["settings.models.label.spellcheck"]</label>
|
|
|
|
|
<select id="spellingModel" @bind="spellingModel">
|
|
|
|
|
@if (availableModels.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
<option value="">@Loc["settings.models.empty"]</option>
|
|
|
|
|
}
|
|
|
|
|
@foreach (var m in availableModels)
|
|
|
|
|
{
|
|
|
|
|
<option value="@m.Name">@m.Name</option>
|
|
|
|
|
}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-07-04 10:32:35 +02:00
|
|
|
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.ollama_server"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="backendSelect">Backend</label>
|
|
|
|
|
<select id="backendSelect" @bind="selectedBackend">
|
|
|
|
|
<option value="Ollama">Ollama</option>
|
|
|
|
|
<option value="llama.cpp">llama.cpp</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="ollamaUrl">@Loc["settings.ollama.label.url"] (Ollama)</label>
|
|
|
|
|
<div class="input-with-button">
|
|
|
|
|
<input id="ollamaUrl" @bind="ollamaUrl" placeholder="http://localhost:11434" />
|
|
|
|
|
<button @onclick="TestConnection" class="btn-sm" disabled="@_isTestingConnection">@Loc["settings.ollama.test"]</button>
|
|
|
|
|
</div>
|
|
|
|
|
@if (_connectionTested)
|
2026-05-31 19:31:27 +02:00
|
|
|
{
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="ollama-status @(_connectionOk ? "ollama-ok" : "ollama-fail")">
|
|
|
|
|
@(_connectionOk
|
|
|
|
|
? $"✓ {_connectionLatency} ms {Loc["settings.ollama.online"]}"
|
|
|
|
|
: $"✗ {Loc["settings.ollama.offline"]}")
|
|
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
}
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="form-hint">@Loc["settings.ollama.hint.url"]</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="llamaCppUrl">URL (llama.cpp)</label>
|
|
|
|
|
<input id="llamaCppUrl" @bind="llamaCppUrl" placeholder="http://localhost:8080" />
|
|
|
|
|
<div class="form-hint">Nur aktiv wenn Backend auf "llama.cpp" gesetzt ist</div>
|
|
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-07-04 10:32:35 +02:00
|
|
|
|
2026-05-16 16:13:35 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-column">
|
|
|
|
|
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.diagnostics"]</h3>
|
|
|
|
|
<button @onclick="RunAllDiagnostics" class="btn-sm" disabled="@_isDiagnosing">@Loc["settings.diagnostics.run"]</button>
|
|
|
|
|
@if (_diagnosticsResults != null)
|
2026-05-16 16:13:35 +02:00
|
|
|
{
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="diagnostics-results">
|
|
|
|
|
@foreach (var line in _diagnosticsResults)
|
|
|
|
|
{
|
|
|
|
|
<div class="diagnostics-line">@line</div>
|
|
|
|
|
}
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-05-16 16:13:35 +02:00
|
|
|
}
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.appearance"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="themeSelect">@Loc["settings.appearance.label.theme"]</label>
|
|
|
|
|
<select id="themeSelect" @bind="selectedTheme">
|
|
|
|
|
<option value="light">@Loc["settings.appearance.theme.light"]</option>
|
|
|
|
|
<option value="dark">@Loc["settings.appearance.theme.dark"]</option>
|
|
|
|
|
<option value="w95">W95 @Loc["settings.appearance.theme.retro"]</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-05-16 16:13:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.language"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="languageSelect">@Loc["settings.language.label"]</label>
|
|
|
|
|
<select id="languageSelect" @bind="selectedLanguage">
|
|
|
|
|
<option value="de">Deutsch</option>
|
|
|
|
|
<option value="en">English</option>
|
|
|
|
|
</select>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-07-04 10:32:35 +02:00
|
|
|
</div>
|
2026-05-20 07:27:41 +02:00
|
|
|
|
2026-05-16 16:13:35 +02:00
|
|
|
</div>
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
</div>
|
|
|
|
|
}
|
2026-06-21 07:52:03 +02:00
|
|
|
@if (_activePromptTab == "novel")
|
2026-05-31 19:31:27 +02:00
|
|
|
{
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-two-column">
|
|
|
|
|
<div class="settings-column">
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.editor_prompt"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="lektorPrompt" @bind="lektorPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@lektorTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(lektorTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => lektorTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(lektorTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(lektorTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-16 16:13:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.factcheck_template"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="factCheckTemplate" @bind="factCheckTemplate" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@factCheckTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(factCheckTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => factCheckTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(factCheckTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(factCheckTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-16 16:13:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.spellcheck_prompt"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="spellingPrompt" @bind="spellingPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@spellingTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(spellingTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => spellingTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(spellingTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(spellingTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.advocatus_diaboli"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="advocatusDiaboliPrompt" @bind="advocatusDiaboliPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@advocatusDiaboliTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(advocatusDiaboliTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => advocatusDiaboliTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(advocatusDiaboliTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(advocatusDiaboliTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-column">
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.hook_finder"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="hookFinderPrompt" @bind="hookFinderPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@hookFinderTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(hookFinderTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => hookFinderTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(hookFinderTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(hookFinderTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.eli5"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="eli5Prompt" @bind="eli5Prompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@eli5Temp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(eli5Temp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => eli5Temp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(eli5Temp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(eli5Temp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.coauthor_brainstorming"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="coAuthorPrompt" @bind="coAuthorPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@coAuthorTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(coAuthorTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => coAuthorTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(coAuthorTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(coAuthorTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
2026-05-20 06:12:54 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-21 07:52:03 +02:00
|
|
|
@if (_activePromptTab == "technical")
|
2026-05-31 19:31:27 +02:00
|
|
|
{
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-two-column">
|
|
|
|
|
<div class="settings-column">
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_lastenheft"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techLastenheftPrompt" @bind="techLastenheftPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techLastenheftTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techLastenheftTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techLastenheftTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techLastenheftTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techLastenheftTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_pflichtenheft"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techPflichtenheftPrompt" @bind="techPflichtenheftPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techPflichtenheftTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techPflichtenheftTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techPflichtenheftTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techPflichtenheftTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techPflichtenheftTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_usecase"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techUseCasePrompt" @bind="techUseCasePrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techUseCaseTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techUseCaseTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techUseCaseTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techUseCaseTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techUseCaseTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_stakeholder"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techStakeholderPrompt" @bind="techStakeholderPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techStakeholderTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techStakeholderTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techStakeholderTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techStakeholderTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techStakeholderTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
|
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-column">
|
|
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_glossar"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techGlossarPrompt" @bind="techGlossarPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techGlossarTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techGlossarTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techGlossarTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techGlossarTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techGlossarTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_qualitaet"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techQualitaetPrompt" @bind="techQualitaetPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techQualitaetTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techQualitaetTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techQualitaetTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techQualitaetTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techQualitaetTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-07-04 10:32:35 +02:00
|
|
|
<div class="settings-section">
|
|
|
|
|
<h3 class="settings-section-title">@Loc["settings.section.tech_risiko"]</h3>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<textarea id="techRisikoPrompt" @bind="techRisikoPrompt" rows="6"></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="temp-slider">
|
|
|
|
|
<label class="temp-label">@Loc["settings.temperature.label"] <span class="temp-value">@techRisikoTemp.ToString("F1")</span></label>
|
|
|
|
|
<input type="range" min="0" max="2" step="0.1" value="@Fmt(techRisikoTemp)" @oninput="@((ChangeEventArgs e) => SetTemp(e, v => techRisikoTemp = v))" />
|
|
|
|
|
<div class="temp-category">@GetTempCategory(techRisikoTemp)</div>
|
|
|
|
|
<div class="temp-desc">@GetTempDescription(techRisikoTemp)</div>
|
|
|
|
|
</div>
|
2026-06-21 07:52:03 +02:00
|
|
|
</div>
|
2026-05-31 19:31:27 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-05-20 06:12:54 +02:00
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
</div>
|
|
|
|
|
|
2026-05-16 16:13:35 +02:00
|
|
|
<div class="settings-actions">
|
2026-05-25 06:38:16 +02:00
|
|
|
<button class="btn btn-primary" @onclick="SaveSettings">@Loc["settings.button.save"]</button>
|
2026-05-16 16:13:35 +02:00
|
|
|
@if (!string.IsNullOrEmpty(statusMessage))
|
|
|
|
|
{
|
|
|
|
|
<span class="settings-status">@statusMessage</span>
|
|
|
|
|
}
|
2026-05-14 12:31:35 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@code {
|
2026-06-21 07:52:03 +02:00
|
|
|
private string _activePromptTab = "novel";
|
|
|
|
|
|
|
|
|
|
private void SwitchToNovelTab() => _activePromptTab = "novel";
|
|
|
|
|
private void SwitchToTechnicalTab() => _activePromptTab = "technical";
|
2026-07-04 10:32:35 +02:00
|
|
|
private void SwitchToAdminTab() => _activePromptTab = "admin";
|
2026-05-14 12:31:35 +02:00
|
|
|
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";
|
2026-05-31 19:31:27 +02:00
|
|
|
private string llamaCppUrl = "http://localhost:8080";
|
|
|
|
|
private string selectedBackend = "Ollama";
|
2026-05-16 16:13:35 +02:00
|
|
|
private string lektorPrompt = string.Empty;
|
|
|
|
|
private string factCheckTemplate = string.Empty;
|
|
|
|
|
private string spellingPrompt = string.Empty;
|
2026-05-20 06:12:54 +02:00
|
|
|
private string advocatusDiaboliPrompt = string.Empty;
|
|
|
|
|
private string hookFinderPrompt = string.Empty;
|
|
|
|
|
private string eli5Prompt = string.Empty;
|
2026-05-31 19:31:27 +02:00
|
|
|
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;
|
2026-05-20 07:27:41 +02:00
|
|
|
private string selectedLanguage = "de";
|
2026-05-14 12:31:35 +02:00
|
|
|
private string newProjectName = string.Empty;
|
2026-05-17 07:27:18 +02:00
|
|
|
private string newUserName = string.Empty;
|
2026-05-14 12:31:35 +02:00
|
|
|
private string statusMessage = string.Empty;
|
2026-05-16 16:13:35 +02:00
|
|
|
private List<LLM_Model> availableModels = new();
|
2026-05-17 07:27:18 +02:00
|
|
|
private List<UserEntity> users = new();
|
|
|
|
|
private int selectedUserId;
|
2026-05-25 06:38:16 +02:00
|
|
|
private string? currentProjectName;
|
2026-05-31 19:31:27 +02:00
|
|
|
private ProjectTemplate? currentProjectTemplate;
|
|
|
|
|
private bool _isAdmin;
|
|
|
|
|
private bool _connectionOk;
|
|
|
|
|
private long _connectionLatency;
|
|
|
|
|
private bool _connectionTested;
|
|
|
|
|
private bool _isTestingConnection;
|
|
|
|
|
private List<string>? _diagnosticsResults;
|
|
|
|
|
private bool _isDiagnosing;
|
2026-05-20 07:27:41 +02:00
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
protected override async Task OnInitializedAsync()
|
|
|
|
|
{
|
2026-05-31 19:31:27 +02:00
|
|
|
var currentUser = AuthService.CurrentUser;
|
2026-06-21 07:52:03 +02:00
|
|
|
_isAdmin = currentUser?.IsAdministrator == true;
|
2026-05-31 19:31:27 +02:00
|
|
|
|
2026-05-17 07:27:18 +02:00
|
|
|
users = await SettingsService.GetUsersAsync();
|
|
|
|
|
var current = await SettingsService.GetCurrentUserAsync();
|
|
|
|
|
selectedUserId = current.Id;
|
|
|
|
|
|
2026-05-25 06:38:16 +02:00
|
|
|
var pid = SettingsService.CurrentProjectId;
|
|
|
|
|
if (pid != null)
|
|
|
|
|
{
|
|
|
|
|
var project = await ProjectService.GetProjectAsync(pid.Value);
|
|
|
|
|
currentProjectName = project?.Name;
|
2026-05-31 19:31:27 +02:00
|
|
|
currentProjectTemplate = project?.Template;
|
2026-05-25 06:38:16 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-20 06:12:54 +02:00
|
|
|
await LoadUserSettings();
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-05-31 19:31:27 +02:00
|
|
|
if (_isAdmin)
|
2026-05-14 12:31:35 +02:00
|
|
|
{
|
2026-05-31 19:31:27 +02:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var models = await OllamaService.GetLocalModelsAsync();
|
|
|
|
|
availableModels = models.ToList();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
availableModels = new();
|
|
|
|
|
}
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 06:12:54 +02:00
|
|
|
private async Task LoadUserSettings()
|
2026-05-17 07:27:18 +02:00
|
|
|
{
|
2026-05-20 06:12:54 +02:00
|
|
|
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";
|
2026-05-31 19:31:27 +02:00
|
|
|
llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080";
|
|
|
|
|
selectedBackend = await SettingsService.GetBackendAsync() ?? "Ollama";
|
2026-05-20 06:12:54 +02:00
|
|
|
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;
|
2026-05-31 19:31:27 +02:00
|
|
|
coAuthorPrompt = await SettingsService.GetCoAuthorBrainstormingPromptAsync() ?? string.Empty;
|
2026-06-21 07:52:03 +02:00
|
|
|
lektorTemp = await SettingsService.GetLektorTempAsync();
|
|
|
|
|
factCheckTemp = await SettingsService.GetFactCheckTempAsync();
|
|
|
|
|
spellingTemp = await SettingsService.GetSpellingTempAsync();
|
|
|
|
|
advocatusDiaboliTemp = await SettingsService.GetAdvocatusDiaboliTempAsync();
|
|
|
|
|
hookFinderTemp = await SettingsService.GetHookFinderTempAsync();
|
|
|
|
|
eli5Temp = await SettingsService.GetEli5TempAsync();
|
2026-05-31 19:31:27 +02:00
|
|
|
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();
|
2026-05-20 07:27:41 +02:00
|
|
|
selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de";
|
2026-05-17 07:27:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SwitchUser(ChangeEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var userId = int.Parse(e.Value?.ToString() ?? "0");
|
|
|
|
|
selectedUserId = userId;
|
2026-05-25 06:38:16 +02:00
|
|
|
SettingsService.SetCurrentUser(userId);
|
|
|
|
|
await SettingsService.InitializeAsync();
|
2026-05-20 06:12:54 +02:00
|
|
|
await LoadUserSettings();
|
2026-05-17 07:27:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task AddUser()
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(newUserName)) return;
|
|
|
|
|
|
|
|
|
|
var user = await SettingsService.CreateUserAsync(newUserName);
|
|
|
|
|
users.Add(user);
|
|
|
|
|
newUserName = string.Empty;
|
2026-05-25 06:38:16 +02:00
|
|
|
statusMessage = string.Format(Loc["settings.status.user_created"], user.Name);
|
2026-05-17 07:27:18 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 12:31:35 +02:00
|
|
|
private async Task SaveSettings()
|
|
|
|
|
{
|
2026-05-31 19:31:27 +02:00
|
|
|
if (_isAdmin)
|
2026-05-20 06:12:54 +02:00
|
|
|
{
|
2026-05-31 19:31:27 +02:00
|
|
|
await SettingsService.SaveOllamaUrlAsync(ollamaUrl);
|
|
|
|
|
await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl);
|
|
|
|
|
await SettingsService.SaveBackendAsync(selectedBackend);
|
|
|
|
|
OllamaService.Reconfigure(ollamaUrl);
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(ragModel))
|
2026-05-20 06:12:54 +02:00
|
|
|
{
|
2026-05-31 19:31:27 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
2026-05-31 19:31:27 +02:00
|
|
|
await SettingsService.SaveRagModelAsync(ragModel);
|
|
|
|
|
await SettingsService.SaveStyleModelAsync(styleModel);
|
|
|
|
|
await SettingsService.SaveSpellingModelAsync(spellingModel);
|
2026-05-20 06:12:54 +02:00
|
|
|
}
|
2026-05-16 16:13:35 +02:00
|
|
|
await SettingsService.SaveThemeAsync(selectedTheme);
|
|
|
|
|
await SettingsService.SaveLektorSystemPromptAsync(lektorPrompt);
|
|
|
|
|
await SettingsService.SaveFactCheckTemplateAsync(factCheckTemplate);
|
|
|
|
|
await SettingsService.SaveSpellingPromptAsync(spellingPrompt);
|
2026-05-20 06:12:54 +02:00
|
|
|
await SettingsService.SaveAdvocatusDiaboliPromptAsync(advocatusDiaboliPrompt);
|
|
|
|
|
await SettingsService.SaveHookFinderPromptAsync(hookFinderPrompt);
|
|
|
|
|
await SettingsService.SaveEli5PromptAsync(eli5Prompt);
|
2026-05-31 19:31:27 +02:00
|
|
|
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);
|
2026-05-20 07:27:41 +02:00
|
|
|
await SettingsService.SaveLanguageAsync(selectedLanguage);
|
2026-05-14 12:31:35 +02:00
|
|
|
|
2026-05-25 06:38:16 +02:00
|
|
|
if (selectedLanguage != Loc.CurrentLanguage)
|
|
|
|
|
await Loc.SetLanguageAsync(selectedLanguage);
|
|
|
|
|
|
|
|
|
|
statusMessage = Loc["settings.status.saved"];
|
2026-05-16 16:13:35 +02:00
|
|
|
await ThemeService.ApplyTheme(selectedTheme);
|
2026-05-14 12:31:35 +02:00
|
|
|
}
|
2026-05-31 19:31:27 +02:00
|
|
|
|
|
|
|
|
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<double> 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<string>();
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-06-21 07:52:03 +02:00
|
|
|
}
|