From 035ecab4863873d3bbd13faae53429de5dba8549 Mon Sep 17 00:00:00 2001 From: Eric Curtin Date: Fri, 4 Sep 2026 14:53:28 +0100 Subject: [PATCH 1/9] Add llmman as a self-hosted host for chat and embedding providers llmman (https://github.com/llmmanorg/llmman) is a local model runner with OpenAI-compatible /v1/ routes, which AI Studio already uses for every self-hosted host. Add a Host.LLMMAN member (appended, so stored values keep their meaning) with the same chat, embedding and model loading flags as its peers. Embedding models are entered manually as for Ollama, since llmman model names need not contain "embed". --- README.md | 2 +- .../Dialogs/EmbeddingProviderDialog.razor.cs | 4 ++-- app/MindWork AI Studio/Plugins/configuration/plugin.lua | 4 ++-- app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs | 1 + app/MindWork AI Studio/Provider/SelfHosted/Host.cs | 3 +++ app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs | 4 ++++ .../Provider/SelfHosted/ProviderSelfHosted.cs | 4 +++- app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md | 1 + 8 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ff72e6787..c2b5492fe 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provi - [IONOS](https://cloud.ionos.com/managed/ai-model-hub) (AI Model Hub running open-source models in Germany) - [LiteLLM](https://www.litellm.ai/) (an AI gateway you run yourself, in front of models from many providers) - [Hugging Face](https://huggingface.co/) using their [inference providers](https://huggingface.co/docs/inference-providers/index) such as Cerebras, Nebius, Sambanova, Novita, Hyperbolic, Together AI, Fireworks, Hugging Face - - Self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), and [vLLM](https://github.com/vllm-project/vllm) + - Self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [vLLM](https://github.com/vllm-project/vllm), and [llmman](https://github.com/llmmanorg/llmman) - [Groq](https://groq.com/) - [Fireworks](https://fireworks.ai/) - For scientists and employees of research institutions, we also support [Helmholtz](https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570) and [GWDG](https://gwdg.de/services/application-services/ai-services/) AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs index f82499d0c..d08798401 100644 --- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor.cs @@ -127,7 +127,7 @@ public EmbeddingProviderDialog() GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName, GetUsedInstanceNames = () => this.UsedInstanceNames, GetHost = () => this.DataHost, - IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is Host.OLLAMA, + IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is (Host.OLLAMA or Host.LLMMAN), }; } @@ -137,7 +137,7 @@ private EmbeddingProvider CreateEmbeddingProviderSettings() Model model = default; if(this.DataLLMProvider is LLMProviders.SELF_HOSTED) { - if (this.DataHost is Host.OLLAMA) + if (this.DataHost is Host.OLLAMA or Host.LLMMAN) model = new Model(this.dataManuallyModel, null); else if (this.DataHost is Host.LM_STUDIO) model = this.DataModel; diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 1ed421b9a..a4146a2b4 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -82,7 +82,7 @@ CONFIG["LLM_PROVIDERS"] = {} -- ["InstanceName"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM +-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and LLMMAN -- ["Host"] = "OLLAMA", -- ["Hostname"] = "", -- @@ -200,7 +200,7 @@ CONFIG["EMBEDDING_PROVIDERS"] = {} -- ["Name"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM +-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and LLMMAN -- ["Host"] = "OLLAMA", -- ["Hostname"] = "", -- diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 20212f930..107dd6470 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -534,6 +534,7 @@ public static bool CanLoadModels(this LLMProviders provider, Host host, string? case Host.OLLAMA: case Host.LM_STUDIO: case Host.VLLM: + case Host.LLMMAN: return true; } } diff --git a/app/MindWork AI Studio/Provider/SelfHosted/Host.cs b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs index d0dde8068..3ecd92d41 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/Host.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs @@ -9,4 +9,7 @@ public enum Host WHISPER_CPP, OLLAMA, VLLM, + + // llmman, local runner with OpenAI-compatible /v1/ routes: https://github.com/llmmanorg/llmman + LLMMAN, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs index 25dc07ca1..7045b2db8 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs @@ -11,6 +11,7 @@ public static class HostExtensions Host.WHISPER_CPP => "whisper.cpp", Host.OLLAMA => "ollama", Host.VLLM => "vLLM", + Host.LLMMAN => "llmman", _ => "Unknown", }; @@ -47,6 +48,7 @@ public static bool IsChatSupported(this Host host) case Host.VLLM: case Host.LM_STUDIO: case Host.LLAMA_CPP: + case Host.LLMMAN: return true; } } @@ -58,6 +60,7 @@ public static bool IsEmbeddingSupported(this Host host) case Host.LM_STUDIO: case Host.OLLAMA: case Host.VLLM: + case Host.LLMMAN: return true; default: @@ -78,6 +81,7 @@ public static bool IsTranscriptionSupported(this Host host) default: case Host.LM_STUDIO: case Host.LLAMA_CPP: + case Host.LLMMAN: return false; } } diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index 4743f87af..2f493843e 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -24,7 +24,7 @@ namespace AIStudio.Provider.SelfHosted; public override string InstanceName { get; set; } = "Self-hosted"; /// - public override bool HasModelLoadingCapability => host is Host.OLLAMA or Host.LM_STUDIO or Host.VLLM or Host.LLAMA_CPP; + public override bool HasModelLoadingCapability => host is Host.OLLAMA or Host.LM_STUDIO or Host.VLLM or Host.LLAMA_CPP or Host.LLMMAN; /// public override async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) @@ -101,6 +101,7 @@ public override async Task GetTextModels(string? apiKeyProvisio case Host.LM_STUDIO: case Host.OLLAMA: case Host.VLLM: + case Host.LLMMAN: return await this.LoadModels( SecretStoreType.LLM_PROVIDER, ["embed"], [], apiKeyProvisional, token); } @@ -128,6 +129,7 @@ public override async Task GetEmbeddingModels(string? apiKeyPro case Host.LM_STUDIO: case Host.OLLAMA: case Host.VLLM: + case Host.LLMMAN: return await this.LoadModels( SecretStoreType.EMBEDDING_PROVIDER, [], ["embed"], apiKeyProvisional, token); } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md index 1ee360db9..e575a5434 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.9.1.md @@ -9,3 +9,4 @@ - Added organization-wide management for tools. IT departments can switch tools off entirely (`DataTools.EnableTools`), disable individual ones (`DataTools.DisabledToolIds`), raise the provider trust a tool requires (`DataTools.MinimumProviderConfidenceByToolId`), and manage every tool setting by tool and field name — either fixed (`DataTools.LockedToolSettings`) or as a pre-filled value the user may still change (`DataTools.DefaultToolSettings`). None of these needs to be known to AI Studio in advance, so it covers the tools future plugins will bring just as well. Secrets such as API keys can be rolled out too, encrypted with your enterprise encryption secret, the same way you already deploy provider keys. The example configuration plugin documents every setting of both tools. - Added tool calling to the abilities you can state yourself in the expert provider settings. When you use a model AI Studio does not recognize as tool-capable, you can now declare that it is, the same way you already could for image input or reasoning. - Improved loading web content in the assistants: it now uses the same reader as the Read Web Page tool, which extracts the main content of a page more reliably and skips navigation and boilerplate. Pages from your own network, including local servers, keep working as before. When a page cannot be read, AI Studio now says why instead of leaving the field empty. +- Added [llmman](https://github.com/llmmanorg/llmman) as a host option for self-hosted chat and embedding providers. llmman is a local model runner; use `http://localhost:17434` as the hostname. \ No newline at end of file From 29b11d68c59d00f09d9b30bd5f718da14280a2d2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 10:12:27 +0200 Subject: [PATCH 2/9] Unified which self-hosted engines can list their models --- .../Provider/LLMProvidersExtensions.cs | 17 +--- .../Provider/SelfHosted/HostExtensions.cs | 31 ++++++ .../Provider/SelfHosted/ProviderSelfHosted.cs | 95 ++++++++----------- .../SelfHosted/HostExtensionsTests.cs | 69 ++++++++++++++ 4 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 app/Tests/Provider/SelfHosted/HostExtensionsTests.cs diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index b314ef1ed..dd42b3cbd 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -557,22 +557,7 @@ private static IProvider CreateProvider(this LLMProviders provider, string insta public static bool CanLoadModels(this LLMProviders provider, Host host, string? apiKey) { if (provider is LLMProviders.SELF_HOSTED) - { - switch (host) - { - case Host.NONE: - case Host.WHISPER_CPP: - default: - return false; - - case Host.LLAMA_CPP: - case Host.OLLAMA: - case Host.LM_STUDIO: - case Host.VLLM: - case Host.LLMMAN: - return true; - } - } + return host.IsModelListSupported(); if(provider is LLMProviders.NONE) return false; diff --git a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs index 7045b2db8..2e2407288 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs @@ -85,4 +85,35 @@ public static bool IsTranscriptionSupported(this Host host) return false; } } + + /// + /// Whether the engine answers the models route with the models it serves. + /// + /// + /// This is the only place which says so. The provider asks it before it loads the models of a + /// chat, embedding, or transcription list, and the dialogs ask it before they offer to reload + /// them; a new engine therefore needs no more than its line here. whisper.cpp is not among + /// them: it serves the one model it was started with and has no route to list it, so the + /// provider offers that model under a fixed name instead. llama.cpp is, even though builds + /// older than the route answer it with 404; the provider handles those builds itself. + /// + /// The engine. + /// True when the models can be loaded from the engine; otherwise, false. + public static bool IsModelListSupported(this Host host) + { + switch (host) + { + case Host.LLAMA_CPP: + case Host.OLLAMA: + case Host.LM_STUDIO: + case Host.VLLM: + case Host.LLMMAN: + return true; + + default: + case Host.NONE: + case Host.WHISPER_CPP: + return false; + } + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index cc2cd0d7b..d16586609 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -25,7 +25,7 @@ namespace AIStudio.Provider.SelfHosted; public override string InstanceName { get; set; } = "Self-hosted"; /// - public override bool HasModelLoadingCapability => host is Host.OLLAMA or Host.LM_STUDIO or Host.VLLM or Host.LLAMA_CPP or Host.LLMMAN; + public override bool HasModelLoadingCapability => host.IsModelListSupported(); /// public override async IAsyncEnumerable StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) @@ -94,23 +94,17 @@ public override async Task GetTextModels(string? apiKeyProvisio { try { - switch (host) - { - case Host.LLAMA_CPP: - return await this.LoadLlamaCppTextModels(apiKeyProvisional, token); - - case Host.LM_STUDIO: - case Host.OLLAMA: - case Host.VLLM: - case Host.LLMMAN: - var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token); - return result with - { - Models = [..result.Models.Where(model => model.IsChatModel(this.Provider))] - }; - } + if (host is Host.LLAMA_CPP) + return await this.LoadLlamaCppTextModels(apiKeyProvisional, token); + + if (!host.IsChatSupported() || !host.IsModelListSupported()) + return ModelLoadResult.FromModels([]); - return ModelLoadResult.FromModels([]); + var result = await this.LoadModels(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token); + return result with + { + Models = [..result.Models.Where(model => model.IsChatModel(this.Provider))] + }; } catch(Exception e) { @@ -129,20 +123,14 @@ public override async Task GetEmbeddingModels(string? apiKeyPro { try { - switch (host) - { - case Host.LM_STUDIO: - case Host.OLLAMA: - case Host.VLLM: - case Host.LLMMAN: - var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token); - return result with - { - Models = [..result.Models.Where(model => model.IsEmbeddingModel(this.Provider))] - }; - } + if (!host.IsEmbeddingSupported() || !host.IsModelListSupported()) + return ModelLoadResult.FromModels([]); - return ModelLoadResult.FromModels([]); + var result = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token); + return result with + { + Models = [..result.Models.Where(model => model.IsEmbeddingModel(this.Provider))] + }; } catch(Exception e) { @@ -156,33 +144,28 @@ public override async Task GetTranscriptionModels(string? apiKe { try { - switch (host) + if (host is Host.WHISPER_CPP) + return ModelLoadResult.FromModels( + [ + new Provider.Model("loaded-model", TB("Model as configured by whisper.cpp")), + ]); + + if (!host.IsTranscriptionSupported() || !host.IsModelListSupported()) + return ModelLoadResult.FromModels([]); + + // + // The engines which get here answer the models endpoint with everything they serve, and + // nothing in that answer says which of them listens. Asking what each model is made for + // is the only thing standing between this list and every chat and embedding model of + // the installation, which is what it used to hold. An engine running no speech model at + // all therefore offers nothing here, and says so, rather than offering models which + // would fail the moment audio reaches them. + // + var result = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token); + return result with { - case Host.WHISPER_CPP: - return ModelLoadResult.FromModels( - [ - new Provider.Model("loaded-model", TB("Model as configured by whisper.cpp")), - ]); - - // - // These two answer the models endpoint with everything they serve, and nothing in - // that answer says which of them listens. Asking what each model is made for is the - // only thing standing between this list and every chat and embedding model of the - // installation, which is what it used to hold. An engine running no speech model at - // all therefore offers nothing here, and says so, rather than offering models which - // would fail the moment audio reaches them. - // - case Host.OLLAMA: - case Host.VLLM: - var result = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, apiKeyProvisional, token); - return result with - { - Models = [..result.Models.Where(model => model.IsTranscriptionModel(this.Provider))] - }; - - default: - return ModelLoadResult.FromModels([]); - } + Models = [..result.Models.Where(model => model.IsTranscriptionModel(this.Provider))] + }; } catch (Exception e) { diff --git a/app/Tests/Provider/SelfHosted/HostExtensionsTests.cs b/app/Tests/Provider/SelfHosted/HostExtensionsTests.cs new file mode 100644 index 000000000..a1187f0d8 --- /dev/null +++ b/app/Tests/Provider/SelfHosted/HostExtensionsTests.cs @@ -0,0 +1,69 @@ +using AIStudio.Provider; +using AIStudio.Provider.SelfHosted; + +namespace AIStudio.Tests.Provider.SelfHosted; + +/// +/// Checks that what each self-hosted engine can do is stated once, and that the statements fit together. +/// +/// +/// Which engines can list their models used to be written down twice, once for the provider and once +/// for the dialogs, and the loading of the chat, embedding, and transcription lists repeated the +/// engines a third time. A new engine had to be added to each of these lists, and one it was +/// forgotten in left a list empty or a reload button disabled without anything failing. Now the +/// host extensions state it, and these cases check that every other answer follows from them. +/// +[TestFixture] +public sealed class HostExtensionsTests +{ + private static readonly Host[] ALL_HOSTS = Enum.GetValues(); + + [TestCaseSource(nameof(ALL_HOSTS))] + public void TheDialogsOfferToLoadModelsExactlyWhereTheEngineCanListThem(Host host) + { + Assert.That(LLMProviders.SELF_HOSTED.CanLoadModels(host, null), Is.EqualTo(host.IsModelListSupported())); + } + + [Test] + public void EveryEngineForChatsCanListItsModels() + { + // + // There is no fixed chat model to offer instead, so such an engine would offer an empty list: + // + var unlisted = ALL_HOSTS.Where(host => host is not Host.NONE && host.IsChatSupported() && !host.IsModelListSupported()); + Assert.That(unlisted, Is.Empty); + } + + [Test] + public void EveryEngineForEmbeddingsCanListItsModels() + { + // + // There is no fixed embedding model to offer instead, so such an engine would offer an empty list: + // + var unlisted = ALL_HOSTS.Where(host => host.IsEmbeddingSupported() && !host.IsModelListSupported()); + Assert.That(unlisted, Is.Empty); + } + + [Test] + public void OnlyWhisperCppTranscribesWithoutListingItsModels() + { + // + // The provider offers the one model of whisper.cpp under a fixed name. Any other engine + // which transcribes but cannot list its models would offer an empty list: + // + var unlisted = ALL_HOSTS.Where(host => host.IsTranscriptionSupported() && !host.IsModelListSupported()); + Assert.That(unlisted, Is.EqualTo(new[] { Host.WHISPER_CPP })); + } + + [Test] + public void TheHostNoneLoadsNoModels() + { + Assert.Multiple(() => + { + Assert.That(Host.NONE.IsModelListSupported(), Is.False); + Assert.That(Host.NONE.IsEmbeddingSupported(), Is.False); + Assert.That(Host.NONE.IsTranscriptionSupported(), Is.False); + Assert.That(LLMProviders.SELF_HOSTED.CanLoadModels(Host.NONE, "a key"), Is.False); + }); + } +} \ No newline at end of file From 5a2cea04165a60cf59dc561a3c2e7a5cb007c0d9 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 10:17:10 +0200 Subject: [PATCH 3/9] Read the reasoning switches of llmman like those of its engines --- .../Provider/Reasoning/ReasoningDispatcher.cs | 7 +++++++ .../Reasoning/ReasoningDispatcherTests.cs | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs b/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs index 0695495a7..5d6cf9731 100644 --- a/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs +++ b/app/MindWork AI Studio/Provider/Reasoning/ReasoningDispatcher.cs @@ -125,6 +125,13 @@ LLMProviders.HELMHOLTZ or Host.VLLM => [ReasoningDialect.OPEN_AI_COMPATIBLE, ReasoningDialect.REASONING_EFFORT, ReasoningDialect.QWEN_THINKING, ReasoningDialect.GOOGLE_THINKING, ReasoningDialect.VLLM], + // + // llmman starts llama.cpp for GGUF models and vLLM, SGLang, or MLX for the others, and + // hands their fields through unchanged. Which engine serves a model is up to llmman, so + // it is read in everything llama.cpp and vLLM are read in: + // + Host.LLMMAN => [ReasoningDialect.OPEN_AI_COMPATIBLE, ReasoningDialect.REASONING_EFFORT, ReasoningDialect.QWEN_THINKING, ReasoningDialect.GOOGLE_THINKING, ReasoningDialect.LLAMA_CPP, ReasoningDialect.VLLM], + _ => EVERYTHING_A_GATEWAY_MIGHT_SERVE, }, diff --git a/app/Tests/Provider/Reasoning/ReasoningDispatcherTests.cs b/app/Tests/Provider/Reasoning/ReasoningDispatcherTests.cs index f9dfe2f06..d0475eb18 100644 --- a/app/Tests/Provider/Reasoning/ReasoningDispatcherTests.cs +++ b/app/Tests/Provider/Reasoning/ReasoningDispatcherTests.cs @@ -46,11 +46,29 @@ public void TheParametersOfAProviderAreReadInTheDialectsItSpeaks(LLMProviders pr [TestCase(Host.LLAMA_CPP, """ "reasoning_budget": 0 """, ReasoningConfigurationState.EXPLICITLY_DISABLED)] [TestCase(Host.VLLM, """ "thinking_token_budget": 2048 """, ReasoningConfigurationState.EXPLICITLY_ENABLED)] [TestCase(Host.VLLM, """ "chat_template_kwargs": { "thinking": true } """, ReasoningConfigurationState.EXPLICITLY_ENABLED)] + [TestCase(Host.LLMMAN, """ "reasoning_budget": 0 """, ReasoningConfigurationState.EXPLICITLY_DISABLED, Description = "llmman serves GGUF models with llama.cpp and hands the budget to it.")] + [TestCase(Host.LLMMAN, """ "thinking_token_budget": 2048 """, ReasoningConfigurationState.EXPLICITLY_ENABLED, Description = "llmman serves other models with vLLM and hands the budget to it.")] public void EachSelfHostedEngineIsReadInItsOwn(Host host, string parameters, ReasoningConfigurationState wanted) { Assert.That(ReasoningDispatcher.WhatTheParametersSay(LLMProviders.SELF_HOSTED, host, parameters), Is.EqualTo(wanted)); } + [Test] + public void LlmmanIsReadInEverythingItsEnginesAreReadIn() + { + // + // llmman decides by itself whether llama.cpp or vLLM serves a model. A dialect either of + // them learns later has to reach llmman as well, or a switch set for llmman goes unseen: + // + var llmman = ReasoningDispatcher.DialectsOf(LLMProviders.SELF_HOSTED, Host.LLMMAN); + + Assert.Multiple(() => + { + Assert.That(llmman, Is.SupersetOf(ReasoningDispatcher.DialectsOf(LLMProviders.SELF_HOSTED, Host.LLAMA_CPP))); + Assert.That(llmman, Is.SupersetOf(ReasoningDispatcher.DialectsOf(LLMProviders.SELF_HOSTED, Host.VLLM))); + }); + } + [Test] public void AProviderIsNotReadInADialectItDoesNotSpeak() { From 503f76aaab57f33838073bc84ea896d4f0ff0794 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 10:19:37 +0200 Subject: [PATCH 4/9] Added transcription through llmman --- app/MindWork AI Studio/Plugins/configuration/plugin.lua | 2 +- app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 17b0f103c..0e35fc6cb 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -184,7 +184,7 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {} -- ["Name"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and WHISPER_CPP +-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, WHISPER_CPP, and LLMMAN -- ["Host"] = "WHISPER_CPP", -- ["Hostname"] = "", -- diff --git a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs index 2e2407288..de711008f 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs @@ -76,12 +76,12 @@ public static bool IsTranscriptionSupported(this Host host) case Host.OLLAMA: case Host.VLLM: case Host.WHISPER_CPP: + case Host.LLMMAN: return true; - + default: case Host.LM_STUDIO: case Host.LLAMA_CPP: - case Host.LLMMAN: return false; } } From fffd9f680a26920f4eb7ca1cb274789b90f53446 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 10:24:10 +0200 Subject: [PATCH 5/9] Recognized the models llmman lists by their registry reference --- app/Tests/Models/Corpus/ModelKindCorpus.cs | 19 +++++++++++++++++++ app/Tests/Models/Hosting/ModelHostTests.cs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/app/Tests/Models/Corpus/ModelKindCorpus.cs b/app/Tests/Models/Corpus/ModelKindCorpus.cs index e91d27bf9..6e7b4ef17 100644 --- a/app/Tests/Models/Corpus/ModelKindCorpus.cs +++ b/app/Tests/Models/Corpus/ModelKindCorpus.cs @@ -66,6 +66,14 @@ public static class ModelKindCorpus new(SELF_HOSTED, "LaBSE", EMBEDDING), new(SELF_HOSTED, "instructor-xl", EMBEDDING), new(SELF_HOSTED, "gtr-t5-large", EMBEDDING), + + // + // llmman lists every model by the full reference it pulled it from: a registry, a namespace, + // the repository, and a tag. Whatever is in front of the model has to come off before the + // word which says what it is for can be found: + // + new(SELF_HOSTED, "docker.io/ai/embeddinggemma:latest", EMBEDDING), + new(SELF_HOSTED, "docker.io/ai/nomic-embed-text-v1.5:latest", EMBEDDING), ]; /// @@ -144,6 +152,9 @@ public static class ModelKindCorpus new(ALIBABA_CLOUD, "qwen3-asr-flash", TRANSCRIPTION), new(ALIBABA_CLOUD, "qwen3-asr-1.7b", TRANSCRIPTION), new(ALIBABA_CLOUD, "qwen-audio-3.0-asr-flash-streaming", TRANSCRIPTION), + + // Voxtral as llmman lists it, from the GGUF build on Hugging Face which llama.cpp serves: + new(SELF_HOSTED, "hf.co/ggml-org/Voxtral-Mini-3B-2507-GGUF:latest", TRANSCRIPTION), ]; /// @@ -395,6 +406,14 @@ public static class ModelKindCorpus new(GROQ, "groq/compound", CHAT), new(GROQ, "groq/compound-mini", CHAT), new(GOOGLE, "gemini-robotics-er-1.5-preview", CHAT), + + // + // Chat models as llmman lists them, with the registry, the namespace, and the tag still in + // front of and behind the model. None of these parts may turn one into something else: + // + new(SELF_HOSTED, "hf.co/unsloth/Qwen3.5-0.8B-GGUF:latest", CHAT), + new(SELF_HOSTED, "docker.io/ai/qwen3.5:0.8b", CHAT), + new(SELF_HOSTED, "hf.co/HuggingFaceTB/SmolLM2-135M-Instruct:latest", CHAT), ]; /// diff --git a/app/Tests/Models/Hosting/ModelHostTests.cs b/app/Tests/Models/Hosting/ModelHostTests.cs index 42f8bd4eb..3e8d4365c 100644 --- a/app/Tests/Models/Hosting/ModelHostTests.cs +++ b/app/Tests/Models/Hosting/ModelHostTests.cs @@ -172,6 +172,25 @@ public void TheVariantOllamaWritesAfterAColonSurvives() Assert.That(unwrapped.Original, Is.EqualTo("qwen3.8:27b-mlx")); } + [TestCase("hf.co/HuggingFaceTB/SmolLM2-135M-Instruct:latest", "SmolLM2-135M-Instruct:latest", ModelVendor.HUGGING_FACE, TestName = "Behind the registry, the organization from the hub names the vendor")] + [TestCase("hf.co/unsloth/Qwen3.5-0.8B-GGUF:latest", "Qwen3.5-0.8B-GGUF:latest", null, TestName = "An organization which only repackaged the weights names nobody")] + [TestCase("docker.io/ai/qwen3.5:0.8b", "qwen3.5:0.8b", null, TestName = "The registry and its namespace come off and the variant stays")] + public void AnLlmmanReferenceLosesItsRegistryAndKeepsItsTag(string modelId, string expectedModel, ModelVendor? expectedVendor) + { + // + // llmman lists the reference it pulled a model from, with the registry in front and the tag + // behind. The registry is a path segment like any organization and comes off the same way. + // The tag is what Ollama writes after its colon, too, and stays for the same reason. + // + var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, modelId, out var vendor); + + Assert.Multiple(() => + { + Assert.That(unwrapped.Original, Is.EqualTo(expectedModel)); + Assert.That(vendor, Is.EqualTo(expectedVendor)); + }); + } + [Test] public void AResellerLeavesTheNameAloneAndOnlyTakesTheApiAway() { From ad6018d29756c5ed38787bb4a4cd468c9b33fbf7 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 10:26:50 +0200 Subject: [PATCH 6/9] Added llmman to the texts and thanked its contributor --- .../Models/Hosting/Hosts/HostSelfHosted.cs | 6 +++++- app/MindWork AI Studio/Pages/Home.razor.cs | 2 +- app/MindWork AI Studio/Pages/Supporters.razor | 1 + app/MindWork AI Studio/Provider/SelfHosted/Host.cs | 2 -- app/MindWork AI Studio/Provider/SelfHosted/Model.cs | 4 ++-- .../Provider/SelfHosted/ProviderSelfHosted.cs | 2 +- app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/MindWork AI Studio/Models/Hosting/Hosts/HostSelfHosted.cs b/app/MindWork AI Studio/Models/Hosting/Hosts/HostSelfHosted.cs index a45f601b9..364232765 100644 --- a/app/MindWork AI Studio/Models/Hosting/Hosts/HostSelfHosted.cs +++ b/app/MindWork AI Studio/Models/Hosting/Hosts/HostSelfHosted.cs @@ -4,12 +4,16 @@ namespace AIStudio.Models.Hosting.Hosts; /// -/// Somebody's own engine: Ollama, LM Studio, vLLM, llama.cpp, or a proxy in front of them. +/// Somebody's own engine: Ollama, LM Studio, vLLM, llama.cpp, llmman, or a proxy in front of them. /// /// /// vLLM serves whatever it was pointed at, and what it was pointed at is usually a hub repository: /// "meta-llama/Llama-3.3-70B-Instruct", "01-ai/yi-large". So the organization comes off here too. /// +/// llmman goes one further and names the registry it pulled the model from: +/// "hf.co/unsloth/Qwen3.5-0.8B-GGUF:latest", "docker.io/ai/qwen3.5:0.8b". The registry is one more +/// path segment and comes off the same way, one segment at a time. +/// /// The colon does not. Ollama writes the variant after it -- "qwen3.8:27b-mlx" -- and taking that /// off would leave a name which no longer says which build of the model is running. Only the host /// which actually has a router treats a colon as routing. diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index a912a3cb0..6031cc81e 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -63,7 +63,7 @@ private void InitializeAdvantagesItems() this.itemsAdvantages = [ new(this.T("Free of charge"), this.T("The app is free to use, both for personal and commercial purposes.")), new(this.T("Democratization of AI"), this.T("We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models.")), - new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")), + new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")), new(this.T("Assistants"), this.T("You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.")), new(T("Tools"), T("AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work.")), new(T("Voice input"), T("Dictate your messages instead of typing them, and have your recordings as well as audio and video files transcribed. You choose the transcription provider, including one you host yourself.")), diff --git a/app/MindWork AI Studio/Pages/Supporters.razor b/app/MindWork AI Studio/Pages/Supporters.razor index 703044b71..227bb5a44 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor +++ b/app/MindWork AI Studio/Pages/Supporters.razor @@ -98,6 +98,7 @@ + diff --git a/app/MindWork AI Studio/Provider/SelfHosted/Host.cs b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs index 3ecd92d41..f565bcc3b 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/Host.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/Host.cs @@ -9,7 +9,5 @@ public enum Host WHISPER_CPP, OLLAMA, VLLM, - - // llmman, local runner with OpenAI-compatible /v1/ routes: https://github.com/llmmanorg/llmman LLMMAN, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/SelfHosted/Model.cs b/app/MindWork AI Studio/Provider/SelfHosted/Model.cs index ce1db8e75..87906f948 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/Model.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/Model.cs @@ -8,8 +8,8 @@ namespace AIStudio.Provider.SelfHosted; /// /// The context window is vLLM's addition to that route: it reports the window the operator started /// the engine with, which is the one number no rule about the weights could ever know. Ollama, -/// LM Studio, and llama.cpp answer the same route without it, so it stays unknown there instead of -/// being guessed. +/// LM Studio, llama.cpp, and llmman answer the same route without it, so it stays unknown there +/// instead of being guessed. /// /// vLLM calls that field max_model_len, which reads like a limit on the model rather than on a /// conversation. The wire keeps their spelling, and this record says what the number means, so that diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index d16586609..95a6ccc45 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -40,7 +40,7 @@ public override async IAsyncEnumerable StreamChatCompletion( { // Build the list of messages. The image format depends on the host: // - Ollama uses the direct image URL format: { "type": "image_url", "image_url": "data:..." } - // - LM Studio, vLLM, and llama.cpp use the nested image URL format: { "type": "image_url", "image_url": { "url": "data:..." } } + // - LM Studio, vLLM, llama.cpp, and llmman use the nested image URL format: { "type": "image_url", "image_url": { "url": "data:..." } } var messages = host switch { Host.OLLAMA => await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.CreateSettingsProvider(effectiveChatModel)), diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md index c91d598bd..2a03a5d1b 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md @@ -8,7 +8,7 @@ - Added a setting to share which features of AI Studio you use with the operators of self-hosted AI servers, usually your organization. When you switch it on, requests to self-hosted providers and LiteLLM name the feature that sent them, such as the chat, an assistant, or an agent, together with the version of AI Studio and your operating system. Cloud providers never receive these details, and AI Studio sends nothing to its developers. The setting is off by default, and you find it in the app settings. Thanks, Dominic Neuburg (`donework`), for this contribution. - Added a way for IT departments to let requests to the self-hosted AI servers of your organization name the feature of AI Studio that sent them, either for everybody or as a default that users may change. This shows which features are used. The Enterprise IT documentation explains what is sent and what to clarify before you switch it on, for example, with your works council. - Added Requesty as a new LLM provider for chats, embeddings, and transcriptions. Requesty is an AI gateway from the United Kingdom that gives you models from many providers with a single API key. Because where your data is processed depends on the model you choose, you set its trust level yourself. Thanks, Thibault Jaigu (`Thibaultjaigu`), for this first contribution. -- Added [llmman](https://github.com/llmmanorg/llmman) as a host option for self-hosted chat and embedding providers. llmman is a local model runner; use `http://localhost:17434` as the hostname. +- Added [llmman](https://github.com/llmmanorg/llmman) as a new option for the models you host yourself. llmman runs open models on your own computer or server, and AI Studio can use them for chats, embeddings, and transcriptions. Thanks, Eric Curtin (`ericcurtin`), for this first contribution. - Improved the Read Web Page tool. When you have not allowed the AI to choose web addresses freely, the tool now refuses every address that does not appear in your chat or in what the tools returned, instead of only asking the AI not to read it. - Improved the installation on macOS: AI Studio is now signed and checked by Apple, so it opens like any other app. You no longer need the Terminal. - Improved the installation on Windows: AI Studio is now signed, so Windows shows who published it. For a while, Windows may still ask you to confirm the installation until it knows the app better. From 66359cc132adacadceead0a6e670e47f5e4958bb Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 13:30:13 +0200 Subject: [PATCH 7/9] Updated I18N --- app/MindWork AI Studio/Assistants/I18N/allTexts.lua | 9 ++++++--- .../plugin.lua | 9 ++++++--- .../plugin.lua | 9 ++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index e2ef58f12..3cd6a56f4 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -9793,6 +9793,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog" -- Tools UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2499909372"] = "Tools" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T251243691"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." + -- AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2587191175"] = "AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work." @@ -9823,9 +9826,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Voice input" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." - -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." @@ -10570,6 +10570,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3364384944"] = "The first 10 supp -- Thank you, Dominic, for supporting us not only financially but also with code: a smoother chat, the audio quality setting for transcriptions, personal API keys for providers of an organization, sharing feature usage with self-hosted servers, and a better Confluence search. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T348735370"] = "Thank you, Dominic, for supporting us not only financially but also with code: a smoother chat, the audio quality setting for transcriptions, personal API keys for providers of an organization, sharing feature usage with self-hosted servers, and a better Confluence search." +-- Thanks, Eric, for bringing llmman to AI Studio as a new option for self-hosted models. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3520213035"] = "Thanks, Eric, for bringing llmman to AI Studio as a new option for self-hosted models." + -- Thanks, Luc, for your build script contribution. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3597955013"] = "Thanks, Luc, for your build script contribution." diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index c8d82e397..a6c7ebf77 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -9795,6 +9795,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Letztes Änderungsproto -- Tools UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2499909372"] = "Werkzeuge" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T251243691"] = "Sie sind nicht an einen einzelnen Anbieter gebunden, sondern können den Anbieter wählen, der am besten zu Ihren Anforderungen passt. Derzeit unterstützen wir OpenAI (GPT-6, GPT-5 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks sowie selbst gehostete Modelle mit vLLM, llama.cpp, ollama, LM Studio oder llmman. Für Forschende und Mitarbeitende von Forschungseinrichtungen unterstützen wir außerdem die KI-Dienste von Helmholtz und der GWDG. Angehörige aller 18 Helmholtz-Zentren, der Max-Planck-Gesellschaft, der meisten deutschen und vieler internationaler Universitäten können diese Dienste über föderierte Anmeldedienste wie eduGAIN nutzen." + -- AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2587191175"] = "KI-Modelle können selbstständig Werkzeuge nutzen, etwa „Websuche“, „Webseite lesen“ oder „Confluence durchsuchen“. Wenn Sie etwas fragen, das ein Modell nicht mit seinem vorhandenen Wissen beantworten kann, schlägt es nach und nennt in seiner Antwort die verwendeten Quellen. Sie entscheiden, welche Werkzeuge ein Modell nutzen darf, und können ihm bei der Arbeit zusehen." @@ -9825,9 +9828,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Datenschutz" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Spracheingabe" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "Sie sind nicht an einen einzigen Anbieter gebunden. Stattdessen können Sie den Anbieter wählen, der am besten zu Ihren Anforderungen passt. Derzeit unterstützen wir OpenAI (GPT-6, GPT-5 usw.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimentell), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks sowie selbst gehostete Modelle mit vLLM, llama.cpp, ollama oder LM Studio. Für Wissenschaftlerinnen und Wissenschaftler sowie Mitarbeitende von Forschungseinrichtungen unterstützen wir außerdem die KI-Dienste von Helmholtz und GWDG. Diese sind über föderierte Logins wie eduGAIN für alle 18 Helmholtz-Zentren, die Max-Planck-Gesellschaft, die meisten deutschen sowie viele internationale Universitäten verfügbar." - -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "Sie können über die Einstellungen zur Anbietervertrauenswürdigkeit steuern, welche Anbieter Ihre Daten erhalten. Zum Beispiel können Sie für das Schreiben von E-Mails einen anderen Schutzlevel festlegen als für allgemeine Chats usw. Außerdem garantieren die meisten Anbieter, dass Ihre Daten nicht zum Trainieren neuer KI-Systeme verwendet werden." @@ -10572,6 +10572,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3364384944"] = "Die ersten 10 Spe -- Thank you, Dominic, for supporting us not only financially but also with code: a smoother chat, the audio quality setting for transcriptions, personal API keys for providers of an organization, sharing feature usage with self-hosted servers, and a better Confluence search. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T348735370"] = "Vielen Dank, Dominic, dass du uns nicht nur finanziell, sondern auch mit Code unterstützt: mit einem reibungsloseren Chat, der Einstellung für die Audioqualität der Transkription, persönlichen API-Schlüsseln für Anbieter einer Organisation, der Möglichkeit, die Funktionsnutzung mit selbst gehosteten Servern zu teilen, und einer besseren Confluence-Suche." +-- Thanks, Eric, for bringing llmman to AI Studio as a new option for self-hosted models. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3520213035"] = "Danke, Eric, dass du llmman als neue Option für selbst gehostete Modelle in AI Studio integriert hast." + -- Thanks, Luc, for your build script contribution. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3597955013"] = "Danke, Luc, für deinen Beitrag zum Build-Skript." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index f6fe74692..f33ca92e3 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -9795,6 +9795,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog" -- Tools UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2499909372"] = "Tools" +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T251243691"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, or llmman. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." + -- AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2587191175"] = "AI models can use tools on their own, such as Web Search, Read Web Page, or Search Confluence. When you ask something a model cannot answer from what it knows, it looks it up and answers with the sources it used. You decide which tools a model may use, and you can watch it work." @@ -9825,9 +9828,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" -- Voice input UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4194938377"] = "Voice input" --- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities. -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T4287735840"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT-6, GPT-5, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hetzner (experimental), IONOS, LiteLLM, Requesty, Hugging Face, Groq, Fireworks, and self-hosted models using vLLM, llama.cpp, ollama, or LM Studio. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities." - -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." @@ -10572,6 +10572,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3364384944"] = "The first 10 supp -- Thank you, Dominic, for supporting us not only financially but also with code: a smoother chat, the audio quality setting for transcriptions, personal API keys for providers of an organization, sharing feature usage with self-hosted servers, and a better Confluence search. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T348735370"] = "Thank you, Dominic, for supporting us not only financially but also with code: a smoother chat, the audio quality setting for transcriptions, personal API keys for providers of an organization, sharing feature usage with self-hosted servers, and a better Confluence search." +-- Thanks, Eric, for bringing llmman to AI Studio as a new option for self-hosted models. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3520213035"] = "Thanks, Eric, for bringing llmman to AI Studio as a new option for self-hosted models." + -- Thanks, Luc, for your build script contribution. UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3597955013"] = "Thanks, Luc, for your build script contribution." From 0bcc43ae37e5f22c346558462f2d4b2ad673f312 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 13:33:00 +0200 Subject: [PATCH 8/9] Fixed the engine names in the example configuration --- app/MindWork AI Studio/Plugins/configuration/plugin.lua | 6 +++--- app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 0e35fc6cb..77218cda9 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -85,7 +85,7 @@ CONFIG["LLM_PROVIDERS"] = {} -- ["InstanceName"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and LLMMAN +-- -- Allowed values for Host are: LM_STUDIO, LLAMA_CPP, OLLAMA, VLLM, and LLMMAN -- ["Host"] = "OLLAMA", -- ["Hostname"] = "", -- @@ -184,7 +184,7 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {} -- ["Name"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, WHISPER_CPP, and LLMMAN +-- -- Allowed values for Host are: OLLAMA, VLLM, WHISPER_CPP, and LLMMAN -- ["Host"] = "WHISPER_CPP", -- ["Hostname"] = "", -- @@ -221,7 +221,7 @@ CONFIG["EMBEDDING_PROVIDERS"] = {} -- ["Name"] = "", -- ["UsedLLMProvider"] = "SELF_HOSTED", -- --- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and LLMMAN +-- -- Allowed values for Host are: LM_STUDIO, OLLAMA, VLLM, and LLMMAN -- ["Host"] = "OLLAMA", -- ["Hostname"] = "", -- diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md index 2a03a5d1b..23a8b7a30 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.10.1.md @@ -51,6 +51,7 @@ - Fixed the warning about unsaved changes in a chat appearing only by chance when you save your chats manually or have turned off workspaces. AI Studio now reliably asks before you open another chat, create a new one, or leave the chat page, so you no longer lose your changes by accident. - Fixed the security notice about removed instructions (prompt injection) appearing only minutes later while AI Studio updated a data source in the background. The notice also mixed your own attachments with files of the data source. Now each notice appears as soon as AI Studio has read the content, for example, the attachments of your message, and it names only that content. - Fixed the security notice about removed instructions (prompt injection) appearing several times for one answer when AI Studio checks which passages of your data sources fit your question: once for every passage it checked, and once more for all of them together. You now get one notice per answer, and a file counts once, however many of its passages were affected. +- Fixed the example configuration for IT departments, which spelled llama.cpp in a way AI Studio does not accept. AI Studio ignored a self-hosted provider set up with that spelling. The example now also names only the self-hosted servers which can create embeddings or transcribe audio. - Updated the code contributions on the supporters page, which now thank everyone who has contributed code to AI Studio so far. - Upgraded several libraries to improve security. - Upgraded to Rust v1.99.0 \ No newline at end of file From 6979dd728dc95ea0bb751e95ee9048062c01468e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 13 Oct 2026 13:35:40 +0200 Subject: [PATCH 9/9] Reduced changelog links to their text for AppStream --- app/Build/Commands/UpdateMetadataCommands.cs | 28 +++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/Build/Commands/UpdateMetadataCommands.cs b/app/Build/Commands/UpdateMetadataCommands.cs index 26fd96410..e0145f9b1 100644 --- a/app/Build/Commands/UpdateMetadataCommands.cs +++ b/app/Build/Commands/UpdateMetadataCommands.cs @@ -679,11 +679,33 @@ private static string ConvertChangelogEntryToAppStream(string changelogEntry) var convertedEntry = new StringBuilder(); for (var index = 0; index < codeSpans.Length; index++) - convertedEntry.Append(index % 2 is 0 ? codeSpans[index] : $"{codeSpans[index]}"); + convertedEntry.Append(index % 2 is 0 ? ConvertLinksToText(codeSpans[index], changelogEntry) : $"{codeSpans[index]}"); return convertedEntry.ToString(); } + /// + /// Reduces the Markdown links of a changelog entry to their text. + /// + /// + /// AppStream descriptions know no links, and the software centers show whatever markup is left as it + /// stands: brackets, parentheses, and the whole address. The address is not lost by dropping it here, + /// because the changelog in the app and the release notes on GitHub still show the link. A link this + /// cannot read, such as one with a code span in its text, stops the conversion rather than reaching + /// the software centers half converted. + /// + /// A part of the entry outside of any code span, already escaped for XML. + /// The whole entry, for the error message. + /// The part with each link replaced by its text. + private static string ConvertLinksToText(string text, string changelogEntry) + { + var convertedText = MarkdownLinkRegex().Replace(text, "${text}"); + if (convertedText.Contains("](", StringComparison.Ordinal)) + throw new InvalidOperationException($"The changelog entry contains a link which cannot be converted: '{changelogEntry}'."); + + return convertedText; + } + private static DateTime ParseMetadataBuildTime(string buildTime) { if (!DateTime.TryParseExact(buildTime.Trim(), "yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var parsedBuildTime)) @@ -1177,4 +1199,8 @@ private sealed record RebuildReleaseState( [GeneratedRegex("^[0-9a-fA-F]{40,64}$")] private static partial Regex GitCommitHashRegex(); + + // Matches one Markdown link. The address may hold one level of parentheses, as Wikipedia addresses do: + [GeneratedRegex("""\[(?[^\[\]]+)\]\((?
(?:[^()\s]|\([^()\s]*\))+)\)""")] + private static partial Regex MarkdownLinkRegex(); }