AuthorBuddy/Components/Pages/Models.razor

205 lines
6.3 KiB
Text
Raw Permalink Normal View History

2026-05-14 12:31:35 +02:00
@page "/models"
2026-05-21 11:04:42 +02:00
@attribute [Authorize]
2026-05-14 12:31:35 +02:00
@inject IOllamaService OllamaService
@inject NavigationManager Navigation
2026-05-25 06:38:16 +02:00
@inject ILocalizationService Loc
2026-05-16 16:13:35 +02:00
2026-05-14 12:31:35 +02:00
2026-05-25 06:38:16 +02:00
<PageTitle>@Loc["page.title.models"]</PageTitle>
2026-05-14 12:31:35 +02:00
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
2026-05-25 06:38:16 +02:00
<h3>@Loc["models.heading"]</h3>
<button @onclick="RefreshModels" disabled="@isLoading">@Loc["models.button.refresh"]</button>
2026-05-14 12:31:35 +02:00
</div>
@if (isLoading)
{
2026-05-25 06:38:16 +02:00
<p>@Loc["models.status.loading"]</p>
2026-05-14 12:31:35 +02:00
}
else if (!string.IsNullOrEmpty(errorMessage))
{
<div style="color: red; margin-bottom: 12px;">@errorMessage</div>
}
else
{
<div class="model-list">
@foreach (var model in localModels)
{
<div class="model-card">
2026-07-04 10:32:35 +02:00
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
<div>
<div class="model-name">@model.Name</div>
<div class="model-details">
@Loc["models.label.size"] @FormatSize(model.Size) |
@Loc["models.label.modified"] @model.ModifiedAt?.ToString("g")
</div>
</div>
<button class="btn-sm" @onclick="() => DeleteModel(model.Name)" style="color:#c0392b;flex-shrink:0;">
Löschen
</button>
2026-05-14 12:31:35 +02:00
</div>
</div>
}
</div>
}
<div style="margin-top: 24px; border-top: 1px solid var(--border-color); padding-top: 16px;">
2026-05-25 06:38:16 +02:00
<h4 style="margin-bottom: 8px;">@Loc["models.pull.heading"]</h4>
2026-05-14 12:31:35 +02:00
<div style="display: flex; gap: 8px; align-items: flex-end;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
2026-05-25 06:38:16 +02:00
<label for="pullModelName">@Loc["models.pull.label.name"]</label>
2026-07-04 10:32:35 +02:00
<input id="pullModelName" @bind="modelToPull" @bind:event="oninput" placeholder="@Loc["models.pull.placeholder"]" />
2026-05-14 12:31:35 +02:00
</div>
2026-07-04 10:32:35 +02:00
<button @onclick="PullModel" disabled="@_pullDisabled">
2026-05-25 06:38:16 +02:00
@Loc["models.pull.button.download"]
2026-05-14 12:31:35 +02:00
</button>
</div>
@if (isPulling)
{
<div style="margin-top: 8px;">
<div class="progress-bar">
<div class="progress-fill" style="width: @pullPercentDisplay"></div>
</div>
<div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div>
</div>
}
</div>
@if (pullResult != null)
{
<div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;">
@pullResult
</div>
}
2026-07-04 10:32:35 +02:00
@if (_confirmDeleteModel != null)
{
<div class="modal-overlay">
<div class="modal-dialog modal-sm">
<div class="modal-title">Modell löschen</div>
<div class="modal-body">
<p>Soll das Modell <strong>@_confirmDeleteModel</strong> wirklich gelöscht werden?</p>
</div>
<div class="modal-actions">
<button @onclick="CancelDelete">Abbrechen</button>
<button @onclick="ConfirmDelete" class="btn-danger">Löschen</button>
</div>
</div>
</div>
}
2026-05-14 12:31:35 +02:00
@code {
2026-05-16 16:13:35 +02:00
private List<LLM_Model> localModels = new();
2026-05-14 12:31:35 +02:00
private bool isLoading;
private string? errorMessage;
private string modelToPull = string.Empty;
private bool isPulling;
2026-07-04 10:32:35 +02:00
private bool _pullDisabled => isPulling || string.IsNullOrWhiteSpace(modelToPull);
2026-05-14 12:31:35 +02:00
private double pullProgress;
private string pullStatus = string.Empty;
private string? pullResult;
private string pullPercentDisplay => $"{pullProgress:F0}%";
2026-07-04 10:32:35 +02:00
private string? _confirmDeleteModel;
2026-05-14 12:31:35 +02:00
protected override async Task OnInitializedAsync()
{
await RefreshModels();
}
private async Task RefreshModels()
{
isLoading = true;
errorMessage = null;
try
{
var models = await OllamaService.GetLocalModelsAsync();
localModels = models.ToList();
}
catch (Exception ex)
{
2026-05-25 06:38:16 +02:00
errorMessage = $"{Loc["models.error.loading"]} {ex.Message}";
2026-05-14 12:31:35 +02:00
localModels = new();
}
finally
{
isLoading = false;
}
}
private async Task PullModel()
{
if (string.IsNullOrWhiteSpace(modelToPull)) return;
isPulling = true;
pullProgress = 0;
2026-05-25 06:38:16 +02:00
pullStatus = Loc["models.pull.status.starting"];
2026-05-14 12:31:35 +02:00
pullResult = null;
try
{
2026-05-16 16:13:35 +02:00
var progress = new Progress<string>(status =>
2026-05-14 12:31:35 +02:00
{
2026-05-16 16:13:35 +02:00
pullStatus = status;
2026-07-04 10:32:35 +02:00
var m = System.Text.RegularExpressions.Regex.Match(status, @"\((\d+)%\)");
if (m.Success && double.TryParse(m.Groups[1].Value, out var pct))
pullProgress = pct;
2026-05-14 12:31:35 +02:00
InvokeAsync(StateHasChanged);
});
await OllamaService.PullModelAsync(modelToPull, progress);
2026-05-25 06:38:16 +02:00
pullResult = $"{Loc["models.pull.status.model"]} '{modelToPull}' {Loc["models.pull.status.success"]}";
2026-05-14 12:31:35 +02:00
modelToPull = string.Empty;
await RefreshModels();
}
catch (Exception ex)
{
2026-05-25 06:38:16 +02:00
pullResult = $"{Loc["common.error"]} {ex.Message}";
2026-05-14 12:31:35 +02:00
}
finally
{
isPulling = false;
}
}
2026-07-04 10:32:35 +02:00
private void DeleteModel(string name)
{
_confirmDeleteModel = name;
}
private void CancelDelete()
{
_confirmDeleteModel = null;
}
private async Task ConfirmDelete()
{
if (_confirmDeleteModel == null) return;
try
{
var deleted = await OllamaService.DeleteModelAsync(_confirmDeleteModel);
if (deleted)
await RefreshModels();
else
errorMessage = $"Fehler beim Löschen von '{_confirmDeleteModel}'";
}
catch (Exception ex)
{
errorMessage = $"Fehler: {ex.Message}";
}
finally
{
_confirmDeleteModel = null;
}
}
2026-05-14 12:31:35 +02:00
private static string FormatSize(long? bytes)
{
2026-05-20 07:27:41 +02:00
if (!bytes.HasValue) return "---";
2026-05-14 12:31:35 +02:00
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB";
return $"{bytes / (1024.0 * 1024 * 1024):F1} GB";
}
}