Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions LocalLLMServerManager.Shared/Services/HuggingFaceSearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,25 @@ public async Task<List<HfFileQuantItem>> FetchQuantizationsAsync(string apiBase,
foreach (var sib in siblings)
{
string rfilename = sib?["rfilename"]?.ToString() ?? "";
if (rfilename.EndsWith(".gguf", StringComparison.OrdinalIgnoreCase))
string lower = rfilename.ToLowerInvariant();
if (lower.EndsWith(".gguf") || lower.EndsWith(".safetensors") || lower.EndsWith(".pt") || lower.EndsWith(".bin") || lower.EndsWith(".onnx"))
{
long size = sib?["size"]?.GetValue<long>() ?? 0L;
double sizeGb = Math.Round(size / (1024.0 * 1024.0 * 1024.0), 2);
string sizeText = sizeGb > 0 ? $"{sizeGb} GB" : "N/A";
string quant = "Q4_K_M";
if (rfilename.Contains("Q8_0", StringComparison.OrdinalIgnoreCase)) quant = "Q8_0";
else if (rfilename.Contains("Q5_K_M", StringComparison.OrdinalIgnoreCase)) quant = "Q5_K_M";
else if (rfilename.Contains("FP16", StringComparison.OrdinalIgnoreCase)) quant = "FP16";
string sizeText = sizeGb > 0 ? $"{sizeGb} GB" : (size > 0 ? $"{Math.Round(size / (1024.0 * 1024.0), 1)} MB" : "N/A");
string quant = "Weight";
if (lower.EndsWith(".gguf"))
{
quant = "Q4_K_M";
if (rfilename.Contains("Q8_0", StringComparison.OrdinalIgnoreCase)) quant = "Q8_0";
else if (rfilename.Contains("Q5_K_M", StringComparison.OrdinalIgnoreCase)) quant = "Q5_K_M";
else if (rfilename.Contains("Q4_0", StringComparison.OrdinalIgnoreCase)) quant = "Q4_0";
else if (rfilename.Contains("FP16", StringComparison.OrdinalIgnoreCase) || rfilename.Contains("F16", StringComparison.OrdinalIgnoreCase)) quant = "FP16";
}
else if (lower.EndsWith(".safetensors")) quant = "Safetensors";
else if (lower.EndsWith(".pt")) quant = "PyTorch";
else if (lower.EndsWith(".onnx")) quant = "ONNX";
else if (lower.EndsWith(".bin")) quant = "Binary";

result.Add(new HfFileQuantItem(rfilename, quant, sizeText, size));
}
Expand All @@ -150,4 +160,5 @@ public async Task<List<HfFileQuantItem>> FetchQuantizationsAsync(string apiBase,

return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,12 @@ public async Task DownloadCivitaiModelAsync(CivitaiModelItem item, string apiBas
ToastService.Instance.Show($"Failed to queue download for '{item.Name}'", ToastType.Error);
}
}

[RelayCommand]
public void OpenInBrowser(CivitaiModelItem? item)
{
if (item == null || item.Id <= 0) return;
BrowserLauncher.OpenUrl($"https://civitai.com/models/{item.Id}");
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ partial void OnActivePresetChanged(string? value)
[ObservableProperty] private double _totalRamMb = 32768.0;

public Action<string, string>? OnInspectModelRequested { get; set; }
public Action<string>? OnPullModelRequested { get; set; }

public HuggingFaceSearchViewModel(IHuggingFaceSearchService hfSearchService)
: this(hfSearchService, new CanIRunItService(), null)
Expand Down Expand Up @@ -588,4 +589,65 @@ public void CloseHfModal()
{
IsHfModalOpen = false;
}

[RelayCommand]
public void OpenInBrowser(string? repoId)
{
if (string.IsNullOrWhiteSpace(repoId)) return;
var safeId = repoId.Trim();
BrowserLauncher.OpenUrl($"https://huggingface.co/{safeId}");
}

[RelayCommand]
public async Task OpenHfModalAsync(HuggingFaceRepoItem? item)
{
if (item == null || string.IsNullOrWhiteSpace(item.Id)) return;
await OpenHfModalAsync(item.Id, ApiBase, HttpHelper.CreateClient(ApiBase));
}

[RelayCommand]
public async Task DownloadHfFileAsync(HfFileQuantItem? file)
{
if (file == null || string.IsNullOrWhiteSpace(file.Filename) || string.IsNullOrWhiteSpace(ModalRepoId)) return;
await DownloadHfFileAsync(file, ApiBase, HttpHelper.CreateClient(ApiBase));
}

public async Task DownloadHfFileAsync(HfFileQuantItem file, string apiBase, HttpClient http)
{
if (file == null || string.IsNullOrWhiteSpace(file.Filename) || string.IsNullOrWhiteSpace(ModalRepoId)) return;

var fileUrl = $"https://huggingface.co/{ModalRepoId}/resolve/main/{file.Filename}";
var pipelineTag = SelectedPipelineTag ?? DetermineModality(ModalRepoId, null);

ToastService.Instance.Show($"Queued download for '{file.Filename}'", ToastType.Info);

try
{
var url = $"{apiBase}/api/hf/download?fileUrl={Uri.EscapeDataString(fileUrl)}&fileName={Uri.EscapeDataString(file.Filename)}&pipelineTag={Uri.EscapeDataString(pipelineTag)}";
var resp = await http.GetAsync(url);
if (resp.IsSuccessStatusCode)
{
ToastService.Instance.Show($"Download started for '{file.Filename}'", ToastType.Success);
}
else
{
ToastService.Instance.Show($"Download failed ({(int)resp.StatusCode}) for '{file.Filename}'", ToastType.Error);
}
}
catch
{
ToastService.Instance.Show($"Failed to queue download for '{file.Filename}'", ToastType.Error);
}
}

[RelayCommand]
public void PullHfGgufInOllama(HfFileQuantItem? file)
{
if (file == null || string.IsNullOrWhiteSpace(ModalRepoId)) return;
string quant = (file.Quantization ?? "").Trim().ToLowerInvariant();
string pullTag = string.IsNullOrEmpty(quant) ? $"hf.co/{ModalRepoId}" : $"hf.co/{ModalRepoId}:{quant}";
OnPullModelRequested?.Invoke(pullTag);
}
}


37 changes: 32 additions & 5 deletions LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ public record HfFileQuantItem(
string FormatSize,
long SizeBytes,
QuickFitBadge? FitBadge = null
);
)
{
public bool IsGguf => (Filename ?? "").EndsWith(".gguf", StringComparison.OrdinalIgnoreCase);
}


public record CivitaiModelItem(
int Id,
Expand Down Expand Up @@ -104,26 +108,39 @@ public HttpClient Http
public void ToggleDocumentationDrawer()
{
Assistant.IsDrawerOpen = false;
Ollama.IsPullDrawerOpen = false;
Documentation.IsDrawerOpen = !Documentation.IsDrawerOpen;
IsAnyDrawerOpen = Documentation.IsDrawerOpen;
UpdateIsAnyDrawerOpen();
}

[RelayCommand]
public void ToggleAiAssistDrawer()
{
Documentation.IsDrawerOpen = false;
Ollama.IsPullDrawerOpen = false;
Assistant.IsDrawerOpen = !Assistant.IsDrawerOpen;
IsAnyDrawerOpen = Assistant.IsDrawerOpen;
if (Assistant.IsDrawerOpen && Assistant.AvailableModelCapabilities.Count <= 1)
{
_ = Assistant.LoadAvailableModelsAsync();
}
UpdateIsAnyDrawerOpen();
}

[RelayCommand]
public void CloseDrawers()
{
Documentation.IsDrawerOpen = false;
Assistant.IsDrawerOpen = false;
IsAnyDrawerOpen = false;
Ollama.ClosePullDrawer();
UpdateIsAnyDrawerOpen();
}

public void UpdateIsAnyDrawerOpen()
{
IsAnyDrawerOpen = Documentation.IsDrawerOpen || Assistant.IsDrawerOpen || Ollama.IsPullDrawerOpen;
}


[ObservableProperty]
private string _appVersionText = $"LocalLLMServerManager v{typeof(MainViewModel).Assembly.GetName().Version?.ToString(3) ?? "3.15.1"} — Unified WASM & Desktop UI";

Expand Down Expand Up @@ -175,9 +192,19 @@ public MainViewModel(
HuggingFace = new HuggingFaceSearchViewModel(hfSearchService, _canIRunItService, telemetryService)
{
ApiBase = ApiBase,
OnInspectModelRequested = (modelName, modality) => NavigateToCanIRunIt(modelName, modality)
OnInspectModelRequested = (modelName, modality) => NavigateToCanIRunIt(modelName, modality),
OnPullModelRequested = model => _ = PullModelAsync(model)
};
Ollama.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(Ollama.IsPullDrawerOpen))
{
OnPropertyChanged(nameof(IsPullDrawerOpen));
UpdateIsAnyDrawerOpen();
}
};
Civitai = new CivitaiSearchViewModel(civitaiSearchService, _canIRunItService, telemetryService)

{
ApiBase = ApiBase,
OnInspectModelRequested = (modelName, modality) => NavigateToCanIRunIt(modelName, modality)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (DataContext is AiAssistantViewModel vm && vm.AvailableModelCapabilities.Count <= 1)
if (DataContext is AiAssistantViewModel vm && vm.IsDrawerOpen && vm.AvailableModelCapabilities.Count <= 1)
{
_ = vm.LoadAvailableModelsAsync();
}
Expand Down Expand Up @@ -165,7 +165,7 @@
if (bitmap != null)
{
using var ms = new MemoryStream();
bitmap.Save(ms);

Check warning on line 168 in LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml.cs

View workflow job for this annotation

GitHub Actions / build-and-test (ubuntu-latest)

'Bitmap.Save(Stream, int?)' is obsolete: 'Use the overload accepting BitmapEncoderOptions instead.'

Check warning on line 168 in LocalLLMServerManager.Shared/Views/Controls/AiAssistantTabControl.axaml.cs

View workflow job for this annotation

GitHub Actions / build-and-test (windows-latest)

'Bitmap.Save(Stream, int?)' is obsolete: 'Use the overload accepting BitmapEncoderOptions instead.'
var bytes = ms.ToArray();
if (bytes.Length > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@
</StackPanel>
</Button>

<Button Command="{Binding $parent[UserControl].((vm:CivitaiSearchViewModel)DataContext).OpenInBrowserCommand}"
CommandParameter="{Binding}"
Classes="matte-secondary"
Padding="8,4"
FontSize="11"
ToolTip.Tip="View model page on CivitAI"
VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="🌐 View on Hub"/>
</StackPanel>
</Button>

<Button Content="⬇️ Download"
Command="{Binding $parent[UserControl].((vm:CivitaiSearchViewModel)DataContext).DownloadCivitaiModelCommand}"
CommandParameter="{Binding}"
Expand Down Expand Up @@ -269,12 +281,25 @@
</StackPanel>
</Button>

<Button Command="{Binding $parent[UserControl].((vm:CivitaiSearchViewModel)DataContext).OpenInBrowserCommand}"
CommandParameter="{Binding}"
Classes="matte-secondary"
Padding="8,4"
FontSize="11"
ToolTip.Tip="View model page on CivitAI"
VerticalAlignment="Center">
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="🌐 View on Hub"/>
</StackPanel>
</Button>

<Button Content="⬇️ Download"
Command="{Binding $parent[UserControl].((vm:CivitaiSearchViewModel)DataContext).DownloadCivitaiModelCommand}"
CommandParameter="{Binding}"
Classes="matte-primary"
Padding="10,4"
FontSize="11"/>

</StackPanel>
</Grid>
</Border>
Expand Down
Loading
Loading