Skip to content

Commit 69446b1

Browse files
authored
feat: InferPage settings (#124)
* Add settings UI for InferPage Introduce a browser-based settings experience and improve model path resolution. Adds a Settings panel (Settings.razor) with styles and JS (settings.css, settings.js) and services to persist settings and API keys (SettingsService, InferPageSettings, SettingsStateService). Wire the settings UX into NavBar and Home to request/show/save settings and apply them at runtime via Utils.ApplySettings; Program.cs now distinguishes CLI-provided config vs browser configuration (Utils.NeedsConfiguration). Update Utils to support manual capability overrides and sanitize model paths, and tweak app CSS and script includes. Improve LLMService local model resolution and path handling (fallback GenericLocalModel, ResolvePath) so unregistered or absolute model paths load correctly. * Refactor settings, utils and startup logic Several refactors and behavior fixes across the app: - NavBar: call SettingsState.RequestSettings directly from the settings button, simplify theme toggle and reload helpers, remove unused flag/methods. - Home: prevent adding image inputs when the selected model doesn't support vision and display an error. - Settings page: build backend options with Prepend for simpler ordering. - Program.cs: validate Self backend model at startup and exit if unsupported; adjust AddMaIN registration flow based on NeedsConfiguration and BackendType. - SettingsService: convert to constructor-injected style, consolidate load/save into generic dict helpers, and add typed methods for saving/getting API keys and model history. - SettingsStateService: shorten comment describing the event bus. - Utils: unify capability checks with a generic GetCapability<T>, make Reason mutually exclusive with ImageGen, and streamline environment variable handling when setting backend API keys. These changes simplify codepaths, centralize settings persistence logic, and enforce model capability checks earlier and at the UI level. * Persist backend profiles with capabilities Add per-backend profile storage (model + capability flags) and use it in settings UI. SettingsService: introduce BackendProfile record and SaveProfileForBackendAsync/GetProfileForBackendAsync using a new storage key. Settings.razor: load profile on backend select, restore manual vision/reasoning/imagegen flags only for unregistered models, fallback to legacy model-history when no profile exists, and save the full profile instead of just the model. This enables preserving capability information per backend while remaining compatible with older model-only entries. * Support MMProj files & extract image uploads Add support for multimodal projector (.mmproj) names across the stack and ensure uploaded image files are treated as images. Implemented MMProjectName on several local vision models and added MmProjName property to InferPage settings, backend profiles, Utils, and ServiceConstants. Settings UI now exposes an MMProj File input for local unregistered vision models and saves/loads it per backend. LLMService now extracts image files from message.Files early (ChatHelper.ExtractImageFromFiles), resolves mmproj name from the model or chat properties to load LLava weights, and requires loaded weights before processing image messages. ChatHelper.ExtractImageFromFiles moves image files into message.Images and cleans up Files so they aren't misrouted to RAG/memory. * Fix vision for local models * Support custom model path and MMProj hints Home: when a custom Model Path is set for Self backend, bake the resolved full file path into a generic local model instance so the LLMService loads from the correct location (creates GenericLocal[Vision][Reasoning]Model variants with FileName set to the resolved path). This fixes cases where Chat.Properties don't reach the service and the model file must be embedded in the model object. Settings: add RegisteredMmProjPathHint to display the expected MMProj file path for registered local vision models (shows a "MMProj file: ..." hint derived from ResolvedModelPathPreview or fallback model directory), keeping the hint in sync with the "Will load:" preview. LLMService: when resolving an mmproj for image models, prefer the directory of a fully-qualified modelKey (custom model file path) and fall back to the configured models path; this ensures mmproj files are located next to custom model files. * CR 1 * versioning * CR 2 - Make settings dialog responsive Update settings dialog sizing in src/MaIN.InferPage/wwwroot/settings.css to use viewport-based width and constraints: set width to 40vw with a min-width of 480px and max-width 90vw, and increase max-height from 85vh to 95vh. This improves layout and usability across different screen sizes. * Fix: Per-call LLama embedder; update Nomic model Switch LLama embedding implementation to create and dispose contexts per call (aligns with LLamaSharp 0.26.0), removing the long-lived Context field and related state. Read EmbeddingSize from a temporary context at construction, call llama_set_embeddings on each per-call context, and normalize embeddings as before. Update LLamaSharpTextEmbedding defaults: use model-default context (ContextSize=0), enable Embeddings, reduce Batch/UBatch sizes, disable FlashAttention, and set pooling + metadata override for older Nomic GGUFs. Update KnownModels and LocalModels entries for the Nomic embedding model (filename, download URL, display name) and change its embedding dimension to 2048. Remove MemoryService's pre/post import context management and adjust MemoryFactory to load weights, inject pooling metadata, and return an embedding config that uses the model's native context. * Fix: Preserve images with file attachments across providers Detect when the latest message contains images and, instead of clearing them, run a memory/kernel search to build contextual text for the LLM. GeminiService and LLMService now: perform SearchAsync, delete the temporary index, aggregate citation text into a context block, inject that context into the message content (nulling Files), call Send, then restore the original message content and return the result. LLMService also performs additional model/resource cleanup when disableCache is true (dispose models/generator and embedder weights, remove model from loader). DeepSeekService and OllamaService no longer null out Images on the message so image data is preserved for the new image-handling flow. * Use mxbai embedding model and add support Replace the default embedding model from Nomic to MXBAI across the codebase. Added a new Mxbai_Embedding LocalModel (mxbai-embedding) implementing IEmbeddingModel with a 1024-dimension embedding and updated metadata/file URL. Kept Nomic_Embedding as a separate local model record. Removed the old KnownModels.GetEmbeddingModel helper and updated MemoryFactory to obtain the embedding model from ModelRegistry ("mxbai-embedding") and adjusted imports accordingly. * Remove unused using directives Clean up unused using statements across several services to reduce compiler warnings and tidy imports.
1 parent a32de60 commit 69446b1

36 files changed

+1357
-256
lines changed

Releases/0.10.1.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# 0.10.1 release
2+
3+
- Added InferPage Settings with backend selection, model configuration, API keys, and per-backend profiles.
4+
- Added custom model paths and multimodal (.mmproj) support for local vision models.
5+
- Refactored settings logic and upgraded LLamaSharp to 0.26.0 for improved vision reliability.

src/MaIN.Core/.nuspec

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package>
33
<metadata>
44
<id>MaIN.NET</id>
5-
<version>0.10.0</version>
5+
<version>0.10.1</version>
66
<authors>Wisedev</authors>
77
<owners>Wisedev</owners>
88
<icon>favicon.png</icon>
@@ -13,9 +13,9 @@
1313
<dependencies>
1414
<dependency id="GTranslate" version="2.3.1" />
1515
<dependency id="HtmlAgilityPack" version="1.12.4" />
16-
<dependency id="LLamaSharp" version="0.25.0" />
17-
<dependency id="LLamaSharp.Backend.Cuda12" version="0.25.0" />
18-
<dependency id="LLamaSharp.kernel-memory" version="0.25.0" />
16+
<dependency id="LLamaSharp" version="0.26.0" />
17+
<dependency id="LLamaSharp.Backend.Cuda12" version="0.26.0" />
18+
<dependency id="LLamaSharp.kernel-memory" version="0.26.0" />
1919
<dependency id="ModelContextProtocol.AspNetCore" version="0.2.0-preview.1" />
2020
<dependency id="Microsoft.SemanticKernel" version="1.68.0" />
2121
<dependency id="Microsoft.SemanticKernel.Connectors.Google" version="1.64.0-alpha" />

src/MaIN.Core/Hub/Contexts/ModelContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal ModelContext(MaINSettings settings, IHttpClientFactory httpClientFactor
3535

3636
public AIModel GetModel(string modelId) => ModelRegistry.GetById(modelId);
3737

38-
public AIModel GetEmbeddingModel() => new Nomic_Embedding();
38+
public AIModel GetEmbeddingModel() => new Mxbai_Embedding();
3939

4040
public bool Exists(string modelId)
4141
{

src/MaIN.Core/MaIN.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
<ItemGroup>
1010
<PackageReference Include="AsyncKeyedLock" Version="8.0.2" />
11-
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.25.0" />
11+
<PackageReference Include="LLamaSharp.Backend.Cuda12" Version="0.26.0" />
1212
<PackageReference Include="Tesseract.Data.English" Version="4.0.0" />
1313
</ItemGroup>
1414

src/MaIN.Domain/MaIN.Domain.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="LLamaSharp" Version="0.25.0" />
11+
<PackageReference Include="LLamaSharp" Version="0.26.0" />
1212
</ItemGroup>
1313

1414
</Project>

src/MaIN.Domain/Models/Concrete/LocalModels.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,32 @@ public sealed record Gemma3_4b() : LocalModel(
1818
new Uri("https://huggingface.co/Inza124/Gemma3-4b/resolve/main/gemma3-4b.gguf?download=true"),
1919
"Gemma3 4B",
2020
8192,
21-
"Balanced 4B model for writing, analysis, and mathematical reasoning");
21+
"Balanced 4B model for writing, analysis, and mathematical reasoning"), IVisionModel
22+
{
23+
public string MMProjectName => "mmproj-model-gemma3-4b.gguf";
24+
}
2225

2326
public sealed record Gemma3_12b() : LocalModel(
2427
"gemma3-12b",
2528
"Gemma3-12b.gguf",
2629
new Uri("https://huggingface.co/Inza124/Gemma3-12b/resolve/main/gemma3-12b.gguf?download=true"),
2730
"Gemma3 12B",
2831
8192,
29-
"Large 12B model for complex analysis, research, and creative writing");
32+
"Large 12B model for complex analysis, research, and creative writing"), IVisionModel
33+
{
34+
public string MMProjectName => "mmproj-model-gemma3-12b.gguf";
35+
}
3036

3137
public sealed record Gemma3n_e4b() : LocalModel(
3238
"gemma3n-e4b",
3339
"Gemma3n-e4b.gguf",
3440
new Uri("https://huggingface.co/Inza124/Gemma-3n-e4b/resolve/main/gemma-3n-e4b.gguf?download=true"),
3541
"Gemma3n E4B",
3642
8192,
37-
"Compact 4B model optimized for efficient reasoning and general-purpose tasks");
43+
"Compact 4B model optimized for efficient reasoning and general-purpose tasks"), IVisionModel
44+
{
45+
public string MMProjectName => "mmproj-model-gemma3n-e4b.gguf";
46+
}
3847

3948
// ===== Llama Family =====
4049

@@ -286,6 +295,17 @@ public sealed record Olmo2_7b() : LocalModel(
286295

287296
// ===== Embedding Model =====
288297

298+
public sealed record Mxbai_Embedding() : LocalModel(
299+
"mxbai-embedding",
300+
"mxbai-embed-large-v1.Q4_K_M.gguf",
301+
new Uri("https://huggingface.co/ChristianAzinn/mxbai-embed-large-v1-gguf/resolve/main/mxbai-embed-large-v1.Q4_K_M.gguf?download=true"),
302+
"mxbai-embed-large v1",
303+
512,
304+
"Model used to generate embeddings with superior knowledge search recall"), IEmbeddingModel
305+
{
306+
public int EmbeddingDimension => 1024;
307+
}
308+
289309
public sealed record Nomic_Embedding() : LocalModel(
290310
"nomic-embedding",
291311
"nomicv2.gguf",

src/MaIN.Domain/Models/SupportedModels.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,6 @@ public static class KnownModels
246246
}
247247
];
248248

249-
public static Model GetEmbeddingModel() =>
250-
new()
251-
{
252-
Name = KnownModelNames.Nomic_Embedding,
253-
FileName = "nomicv2.gguf",
254-
Description = "Model used to generate embeddings.",
255-
DownloadUrl = "https://huggingface.co/Inza124/Nomic/resolve/main/nomicv2.gguf?download=true",
256-
};
257-
258249
public static bool IsModelSupported(string name) =>
259250
Models.Any(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
260251
|| x.Name.Replace(':', '-').Equals(name,

src/MaIN.InferPage/Components/App.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<script src="_framework/blazor.web.js"></script>
2828
<script src="scroll.js"></script>
2929
<script src="editor.js"></script>
30+
<script src="settings.js"></script>
3031
<script>
3132
window.themeManager = {
3233
save: function (theme) { localStorage.setItem('theme', theme); },

src/MaIN.InferPage/Components/Layout/MainLayout.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@inherits LayoutComponentBase
1+
@inherits LayoutComponentBase
22
<NavBar/>
33
<div class="content">
44

@@ -9,4 +9,4 @@
99
An unhandled error has occurred.
1010
<a href="." class="reload">Reload</a>
1111
<span class="dismiss">🗙</span>
12-
</div>
12+
</div>

src/MaIN.InferPage/Components/Layout/NavBar.razor

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular
22
@using MaIN.Domain.Configuration
3+
@using MaIN.InferPage.Services
34
@inject NavigationManager _navigationManager
45
@inject IJSRuntime JS
6+
@inject SettingsStateService SettingsState
7+
@implements IDisposable
58
@rendermode @(new InteractiveServerRenderMode(prerender: false))
69

710
<FluentDesignTheme @bind-Mode="@Mode"
@@ -48,6 +51,11 @@
4851
Style="margin-left: 10px">Vision 👁️</FluentBadge>
4952
}
5053
<div style="margin-left: auto; align-self: flex-end;">
54+
<FluentButton Style="background-color: transparent;"
55+
BackgroundColor="rgba(0, 0, 0, 0)"
56+
Appearance="Appearance.Lightweight"
57+
OnClick="@(() => SettingsState.RequestSettings())" IconStart="@(new Icons.Regular.Size24.Settings().WithColor(AccentColor))">
58+
</FluentButton>
5159
<FluentButton Style="padding: 10px; background-color: transparent;"
5260
BackgroundColor="rgba(0, 0, 0, 0)"
5361
Appearance="Appearance.Lightweight"
@@ -64,30 +72,28 @@
6472
@code {
6573
private DesignThemeModes Mode { get; set; }
6674
private string AccentColor => Mode == DesignThemeModes.Dark ? "#00ffcc" : "#00cca3";
67-
private bool _isChangingTheme = false;
6875

6976
protected override async Task OnAfterRenderAsync(bool firstRender)
7077
{
7178
if (firstRender)
7279
{
7380
var stored = await JS.InvokeAsync<string>("themeManager.load");
7481
Mode = stored == "dark" ? DesignThemeModes.Dark : DesignThemeModes.Light;
82+
SettingsState.OnSettingsApplied += OnSettingsChanged;
7583
StateHasChanged();
7684
}
7785
}
7886

79-
private void SetTheme()
87+
private void OnSettingsChanged()
8088
{
81-
if (_isChangingTheme) return;
82-
_isChangingTheme = true;
83-
Mode = Mode == DesignThemeModes.Dark ? DesignThemeModes.Light : DesignThemeModes.Dark;
84-
_isChangingTheme = false;
89+
InvokeAsync(StateHasChanged);
8590
}
8691

87-
private void Reload(MouseEventArgs obj)
88-
{
89-
_navigationManager.Refresh(true);
90-
}
92+
private void SetTheme()
93+
=> Mode = Mode == DesignThemeModes.Dark ? DesignThemeModes.Light : DesignThemeModes.Dark;
94+
95+
private void Reload()
96+
=> _navigationManager.Refresh(true);
9197

9298
private string GetBackendColor()
9399
{
@@ -103,4 +109,9 @@
103109
_ => Utils.BackendType.ToString()
104110
};
105111
}
112+
113+
public void Dispose()
114+
{
115+
SettingsState.OnSettingsApplied -= OnSettingsChanged;
116+
}
106117
}

0 commit comments

Comments
 (0)