diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 3c8925f6..9b3f46f6 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -8896,6 +8896,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "The trust leve -- You or your organization operate the LLM locally or within your trusted network. In terms of data processing and security, this is the best possible way. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T2124364471"] = "You or your organization operate the LLM locally or within your trusted network. In terms of data processing and security, this is the best possible way." +-- The provider operates its service in the EU and is subject to the **GDPR** (General Data Protection Regulation). It provides access to **open source models**. However, the service is currently **experimental**, and performance and availability are not guaranteed. We have no provider-specific information about whether submitted data is used for training. +UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T2930312134"] = "The provider operates its service in the EU and is subject to the **GDPR** (General Data Protection Regulation). It provides access to **open source models**. However, the service is currently **experimental**, and performance and availability are not guaranteed. We have no provider-specific information about whether submitted data is used for training." + -- The provider is located in the EU and is subject to the **GDPR** (General Data Protection Regulation). Additionally, the provider states that **your data is not used for training**. UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T3010553924"] = "The provider is located in the EU and is subject to the **GDPR** (General Data Protection Regulation). Additionally, the provider states that **your data is not used for training**." diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 6851b529..f0337abb 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -634,7 +634,7 @@ CONFIG["SETTINGS"] = {} -- Configure a custom confidence scheme. -- This is used when DataConfidence.ConfidenceScheme is set to CUSTOM. -- Allowed provider keys are: OPEN_AI, ANTHROPIC, MISTRAL, GOOGLE, X, DEEP_SEEK, ALIBABA_CLOUD, --- PERPLEXITY, OPEN_ROUTER, FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, HELMHOLTZ, GWDG +-- PERPLEXITY, OPEN_ROUTER, HETZNER, FIREWORKS, GROQ, HUGGINGFACE, SELF_HOSTED, HELMHOLTZ, GWDG -- Allowed confidence values are: UNTRUSTED, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH -- -- Replaces, does not merge: a configuration with a higher priority replaces the whole @@ -651,6 +651,7 @@ CONFIG["SETTINGS"] = {} -- ["ALIBABA_CLOUD"] = "LOW", -- ["PERPLEXITY"] = "MODERATE", -- ["OPEN_ROUTER"] = "MODERATE", +-- ["HETZNER"] = "HIGH", -- ["FIREWORKS"] = "MODERATE", -- ["GROQ"] = "MODERATE", -- ["HUGGINGFACE"] = "MODERATE", diff --git a/app/MindWork AI Studio/Provider/Confidence.cs b/app/MindWork AI Studio/Provider/Confidence.cs index a15fd9ed..600a6e5b 100644 --- a/app/MindWork AI Studio/Provider/Confidence.cs +++ b/app/MindWork AI Studio/Provider/Confidence.cs @@ -64,6 +64,12 @@ public sealed record Confidence Level = ConfidenceLevel.MEDIUM, Description = TB("The provider is located in the EU and is subject to the **GDPR** (General Data Protection Regulation). Additionally, the provider states that **your data is not used for training**."), }; + + public static readonly Confidence GDPR_EXPERIMENTAL_OPEN_SOURCE = new() + { + Level = ConfidenceLevel.MEDIUM, + Description = TB("The provider operates its service in the EU and is subject to the **GDPR** (General Data Protection Regulation). It provides access to **open source models**. However, the service is currently **experimental**, and performance and availability are not guaranteed. We have no provider-specific information about whether submitted data is used for training."), + }; public static readonly Confidence SELF_HOSTED = new() { diff --git a/app/MindWork AI Studio/Provider/Hetzner/ProviderHetzner.cs b/app/MindWork AI Studio/Provider/Hetzner/ProviderHetzner.cs new file mode 100644 index 00000000..a2004f23 --- /dev/null +++ b/app/MindWork AI Studio/Provider/Hetzner/ProviderHetzner.cs @@ -0,0 +1,93 @@ +using System.Runtime.CompilerServices; + +using AIStudio.Chat; +using AIStudio.Provider.OpenAI; +using AIStudio.Settings; + +namespace AIStudio.Provider.Hetzner; + +public sealed class ProviderHetzner() : BaseProvider(LLMProviders.HETZNER, new Uri("https://inference.hetzner.com/api/v1/"), ExternalHttpTrustPolicy.SYSTEM_TRUST_ONLY, LOGGER) +{ + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + + #region Implementation of IProvider + + /// + public override string Id => LLMProviders.HETZNER.ToSecretId(); + + /// + public override string InstanceName { get; set; } = "Hetzner (Experimental)"; + + /// + public override bool HasModelLoadingCapability => true; + + /// + public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) + { + await foreach (var content in this.StreamOpenAICompatibleChatCompletion( + "Hetzner", + chatModel, + chatThread, + settingsManager, + async (systemPrompt, apiParameters) => + { + var messages = await chatThread.Blocks.BuildMessagesUsingNestedImageUrlAsync(this.Provider, chatModel); + + return new ChatCompletionAPIRequest + { + Model = chatModel.Id, + Messages = [systemPrompt, ..messages], + Stream = true, + AdditionalApiParameters = apiParameters + }; + }, + token: token)) + yield return content; + } + + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + /// + public override async IAsyncEnumerable StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + { + yield break; + } + #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + /// + public override Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) + { + return Task.FromResult(TranscriptionResult.Failure()); + } + + /// + public override Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>([]); + } + + /// + public override Task GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return this.LoadModelsResponse(SecretStoreType.LLM_PROVIDER, "models", modelResponse => modelResponse.Data, token, apiKeyProvisional); + } + + /// + public override Task GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(ModelLoadResult.FromModels([])); + } + + /// + public override Task GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(ModelLoadResult.FromModels([])); + } + + /// + public override Task GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(ModelLoadResult.FromModels([])); + } + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/LLMProviders.cs b/app/MindWork AI Studio/Provider/LLMProviders.cs index 6a560036..740b2f4b 100644 --- a/app/MindWork AI Studio/Provider/LLMProviders.cs +++ b/app/MindWork AI Studio/Provider/LLMProviders.cs @@ -16,6 +16,7 @@ public enum LLMProviders ALIBABA_CLOUD = 12, PERPLEXITY = 14, OPEN_ROUTER = 15, + HETZNER = 16, FIREWORKS = 5, GROQ = 6, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 92a7860d..4cb91ac1 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -6,6 +6,7 @@ using AIStudio.Provider.Google; using AIStudio.Provider.Groq; using AIStudio.Provider.GWDG; using AIStudio.Provider.Helmholtz; +using AIStudio.Provider.Hetzner; using AIStudio.Provider.HuggingFace; using AIStudio.Provider.Mistral; using AIStudio.Provider.OpenAI; @@ -56,6 +57,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => "Alibaba Cloud", LLMProviders.PERPLEXITY => "Perplexity", LLMProviders.OPEN_ROUTER => "OpenRouter", + LLMProviders.HETZNER => "Hetzner (Experimental)", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -91,6 +93,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => "Alibaba Cloud", LLMProviders.PERPLEXITY => "Perplexity", LLMProviders.OPEN_ROUTER => "OpenRouter", + LLMProviders.HETZNER => "Hetzner", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -144,6 +147,12 @@ public static class LLMProvidersExtensions LLMProviders.OPEN_ROUTER => Confidence.USA_HUB.WithRegion("America, U.S.").WithSources("https://openrouter.ai/privacy", "https://openrouter.ai/terms").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.HETZNER => Confidence.GDPR_EXPERIMENTAL_OPEN_SOURCE.WithRegion("Europe, Germany").WithSources( + "https://experiments.hetzner.com/docs/inference", + "https://www.hetzner.com/legal/privacy-policy/", + "https://www.hetzner.com/legal/terms-and-conditions/" + ).WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.SELF_HOSTED => Confidence.SELF_HOSTED.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), LLMProviders.HELMHOLTZ => Confidence.GDPR_NO_TRAINING.WithRegion("Europe, Germany").WithSources("https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), @@ -180,6 +189,7 @@ public static class LLMProvidersExtensions LLMProviders.HUGGINGFACE => false, LLMProviders.PERPLEXITY => false, LLMProviders.OPEN_ROUTER => true, + LLMProviders.HETZNER => false, // // Self-hosted providers are treated as a special case anyway. @@ -209,6 +219,7 @@ public static class LLMProvidersExtensions // Providers that do not support transcription: // LLMProviders.OPEN_ROUTER => false, + LLMProviders.HETZNER => false, LLMProviders.GROQ => false, LLMProviders.ANTHROPIC => false, LLMProviders.X => false, @@ -271,6 +282,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.PERPLEXITY => new ProviderPerplexity { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.OPEN_ROUTER => new ProviderOpenRouter { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, + LLMProviders.HETZNER => new ProviderHetzner { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.GROQ => new ProviderGroq { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, LLMProviders.FIREWORKS => new ProviderFireworks { InstanceName = instanceName, ConfiguredProviderId = configuredProviderId, AdditionalJsonApiParameters = expertProviderApiParameter, IsEnterpriseConfiguration = isEnterpriseConfiguration }, @@ -302,6 +314,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => "https://account.alibabacloud.com/register/intl_register.htm", LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api", LLMProviders.OPEN_ROUTER => "https://openrouter.ai/keys", + LLMProviders.HETZNER => "https://experiments.hetzner.com", LLMProviders.GROQ => "https://console.groq.com/", LLMProviders.FIREWORKS => "https://fireworks.ai/login", @@ -327,6 +340,7 @@ public static class LLMProvidersExtensions LLMProviders.PERPLEXITY => "https://www.perplexity.ai/account/api/", LLMProviders.OPEN_ROUTER => "https://openrouter.ai/activity", LLMProviders.HUGGINGFACE => "https://huggingface.co/settings/billing", + LLMProviders.HETZNER => "https://experiments.hetzner.com", _ => string.Empty, }; @@ -345,6 +359,7 @@ public static class LLMProvidersExtensions LLMProviders.PERPLEXITY => true, LLMProviders.OPEN_ROUTER => true, LLMProviders.HUGGINGFACE => true, + LLMProviders.HETZNER => true, _ => false, }; @@ -422,6 +437,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => true, LLMProviders.PERPLEXITY => true, LLMProviders.OPEN_ROUTER => true, + LLMProviders.HETZNER => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, @@ -445,6 +461,7 @@ public static class LLMProvidersExtensions LLMProviders.ALIBABA_CLOUD => true, LLMProviders.PERPLEXITY => true, LLMProviders.OPEN_ROUTER => true, + LLMProviders.HETZNER => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, diff --git a/app/MindWork AI Studio/Settings/ProviderExtensions.Reasoning.cs b/app/MindWork AI Studio/Settings/ProviderExtensions.Reasoning.cs index ec95ee9b..437aae9b 100644 --- a/app/MindWork AI Studio/Settings/ProviderExtensions.Reasoning.cs +++ b/app/MindWork AI Studio/Settings/ProviderExtensions.Reasoning.cs @@ -90,6 +90,7 @@ public static partial class ProviderExtensions GetQwenReasoningState(parameters)), LLMProviders.OPEN_ROUTER or + LLMProviders.HETZNER or LLMProviders.X or LLMProviders.DEEP_SEEK or LLMProviders.GROQ or diff --git a/app/MindWork AI Studio/Settings/ProviderExtensions.cs b/app/MindWork AI Studio/Settings/ProviderExtensions.cs index 3d18e586..6dc38cff 100644 --- a/app/MindWork AI Studio/Settings/ProviderExtensions.cs +++ b/app/MindWork AI Studio/Settings/ProviderExtensions.cs @@ -54,6 +54,7 @@ public static partial class ProviderExtensions LLMProviders.ALIBABA_CLOUD => GetModelCapabilitiesAlibaba(model), LLMProviders.PERPLEXITY => GetModelCapabilitiesPerplexity(model), LLMProviders.OPEN_ROUTER => GetModelCapabilitiesOpenRouter(model), + LLMProviders.HETZNER => GetModelCapabilitiesOpenSource(model), LLMProviders.GROQ => GetModelCapabilitiesOpenSource(model), LLMProviders.FIREWORKS => GetModelCapabilitiesOpenSource(model), diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 43d86255..09081ff0 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -599,8 +599,9 @@ public sealed class SettingsManager { LLMProviders.SELF_HOSTED => ConfidenceLevel.HIGH, LLMProviders.DEEP_SEEK => ConfidenceLevel.LOW, + LLMProviders.ALIBABA_CLOUD => ConfidenceLevel.LOW, - _ => ConfidenceLevel.MEDIUM, + _ => ConfidenceLevel.MEDIUM, }; case ConfidenceSchemes.TRUST_USA: @@ -610,7 +611,9 @@ public sealed class SettingsManager LLMProviders.MISTRAL => ConfidenceLevel.LOW, LLMProviders.HELMHOLTZ => ConfidenceLevel.LOW, LLMProviders.GWDG => ConfidenceLevel.LOW, + LLMProviders.HETZNER => ConfidenceLevel.LOW, LLMProviders.DEEP_SEEK => ConfidenceLevel.LOW, + LLMProviders.ALIBABA_CLOUD => ConfidenceLevel.LOW, _ => ConfidenceLevel.MEDIUM, }; @@ -622,6 +625,7 @@ public sealed class SettingsManager LLMProviders.MISTRAL => ConfidenceLevel.MEDIUM, LLMProviders.HELMHOLTZ => ConfidenceLevel.MEDIUM, LLMProviders.GWDG => ConfidenceLevel.MEDIUM, + LLMProviders.HETZNER => ConfidenceLevel.MEDIUM, _ => ConfidenceLevel.LOW, }; @@ -631,6 +635,7 @@ public sealed class SettingsManager { LLMProviders.SELF_HOSTED => ConfidenceLevel.HIGH, LLMProviders.DEEP_SEEK => ConfidenceLevel.MEDIUM, + LLMProviders.ALIBABA_CLOUD => ConfidenceLevel.MEDIUM, _ => ConfidenceLevel.LOW, }; diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.8.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.8.1.md index 411d53d7..85608d7b 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.8.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.8.1.md @@ -1,4 +1,5 @@ # v26.8.1, build 251 (2026-08-xx xx:xx UTC) +- Added Hetzner's experimental inference API as an LLM provider. It runs open-source models in the EU and supports text and image chats through its OpenAI-compatible API. - Added a prototype Visual Briefing Assistant that turns documents, data, images, audio, and video into self-contained interactive HTML briefings. When you want to test it, you have to enable this preview feature in your app settings. - Added organization-configurable defaults and visibility controls for the Visual Briefing Assistant. - Added a share button for assistants, configurations, and language plugins. It uses the native share dialog on Windows and macOS. For Linux, we added an export option, which stores the plugin archive at a location of your choice. When you work on a translation for a new language, you can now hand your current state to testers or to us with one click.