diff --git a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs index 99513c21..2cd90e5b 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/ToolSettingsDialog.razor.cs @@ -52,11 +52,19 @@ public partial class ToolSettingsDialog : SettingsDialogBase return string.Format(T("{0} Default: {1}"), description, defaultValue); } - private bool IsFieldDisabled(string fieldName) => - this.toolDefinition?.Id.Equals(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, StringComparison.Ordinal) is true && - fieldName.Equals("allowedPrivateHosts", StringComparison.Ordinal) && - ManagedConfiguration.TryGet(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, out var meta) && - meta.IsLocked; + private bool IsFieldDisabled(string fieldName) + { + if (this.toolDefinition?.Id.Equals(ToolSelectionRules.WEB_SEARCH_TOOL_ID, StringComparison.Ordinal) is true && + fieldName.Equals("baseUrl", StringComparison.Ordinal) && + ManagedConfiguration.TryGet(x => x.Tools, x => x.WebSearchBaseUrl, out var webSearchMeta) && + webSearchMeta.IsLocked) + return true; + + return this.toolDefinition?.Id.Equals(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, StringComparison.Ordinal) is true && + fieldName.Equals("allowedPrivateHosts", StringComparison.Ordinal) && + ManagedConfiguration.TryGet(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, out var readWebPageMeta) && + readWebPageMeta.IsLocked; + } private string GetFieldPlaceholder(string fieldName, ToolSettingsFieldDefinition fieldDefinition) => string.IsNullOrWhiteSpace(this.GetValue(fieldName)) ? this.GetFieldDefaultValue(fieldName, fieldDefinition) : string.Empty; diff --git a/app/MindWork AI Studio/Plugins/configuration/plugin.lua b/app/MindWork AI Studio/Plugins/configuration/plugin.lua index 042d4d3d..9971d138 100644 --- a/app/MindWork AI Studio/Plugins/configuration/plugin.lua +++ b/app/MindWork AI Studio/Plugins/configuration/plugin.lua @@ -269,6 +269,11 @@ CONFIG["SETTINGS"] = {} -- ["read_web_page"] = "MEDIUM" -- } +-- Configure the SearXNG instance URL used by the Web Search tool. +-- You can enter either the instance root URL or the /search endpoint. +-- CONFIG["SETTINGS"]["DataTools.WebSearchBaseUrl"] = "https://searxng.website/" +-- CONFIG["SETTINGS"]["DataTools.WebSearchBaseUrl.AllowUserOverride"] = false + -- Configure private or VPN hosts that the Read Web Page tool may access. -- Public web pages do not need to be listed here. -- Private hosts listed here still require a provider with HIGH confidence before any page content is sent to the model. diff --git a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs index ef4f9220..b8e8ae6c 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataTools.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataTools.cs @@ -21,6 +21,11 @@ public sealed class DataTools(Expression>? configSelection x => x.MinimumProviderConfidenceByToolId, new Dictionary(StringComparer.Ordinal)); + public string WebSearchBaseUrl { get; set; } = ManagedConfiguration.Register( + configSelection, + x => x.WebSearchBaseUrl, + string.Empty); + public string ReadWebPageAllowedPrivateHosts { get; set; } = ManagedConfiguration.Register( configSelection, x => x.ReadWebPageAllowedPrivateHosts, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index d84ec0f4..d225189e 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -175,6 +175,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Config: minimum provider confidence per tool ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, this.Id, settingsTable, dryRun); + // Config: SearXNG base URL for the web search tool + ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.WebSearchBaseUrl, this.Id, settingsTable, dryRun); + // Config: private hosts allowed for the read web page tool ManagedConfiguration.TryProcessConfiguration(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, this.Id, settingsTable, dryRun); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index cff987cf..5e251f72 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -249,6 +249,10 @@ public static partial class PluginFactory if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.MinimumProviderConfidenceByToolId, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; + // Check for the SearXNG base URL for the web search tool: + if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.WebSearchBaseUrl, AVAILABLE_PLUGINS)) + wasConfigurationChanged = true; + // Check for private hosts allowed for the read web page tool: if(ManagedConfiguration.IsConfigurationLeftOver(x => x.Tools, x => x.ReadWebPageAllowedPrivateHosts, AVAILABLE_PLUGINS)) wasConfigurationChanged = true; diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs index 94c0da96..29302a03 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolSettingsService.cs @@ -5,6 +5,7 @@ namespace AIStudio.Tools.ToolCallingSystem; public sealed class ToolSettingsService(SettingsManager settingsManager, RustService rustService) { + private const string WEB_SEARCH_BASE_URL_FIELD = "baseUrl"; private const string READ_WEB_PAGE_ALLOWED_PRIVATE_HOSTS_FIELD = "allowedPrivateHosts"; public async Task> GetSettingsAsync(ToolDefinition definition) @@ -15,6 +16,12 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer { var fieldName = property.Key; var fieldDefinition = property.Value; + if (IsWebSearchBaseUrlField(definition, fieldName)) + { + values[fieldName] = settingsManager.ConfigurationData.Tools.WebSearchBaseUrl; + continue; + } + if (IsReadWebPageAllowedPrivateHostsField(definition, fieldName)) { values[fieldName] = settingsManager.ConfigurationData.Tools.ReadWebPageAllowedPrivateHosts; @@ -87,6 +94,14 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer values.TryGetValue(fieldName, out var value); value ??= string.Empty; + if (IsWebSearchBaseUrlField(definition, fieldName)) + { + if (!IsWebSearchBaseUrlLocked()) + settingsManager.ConfigurationData.Tools.WebSearchBaseUrl = value; + + continue; + } + if (IsReadWebPageAllowedPrivateHostsField(definition, fieldName)) { if (!IsReadWebPageAllowedPrivateHostsLocked()) @@ -113,6 +128,13 @@ public sealed class ToolSettingsService(SettingsManager settingsManager, RustSer await MessageBus.INSTANCE.SendMessage(null, Event.CONFIGURATION_CHANGED, null); } + private static bool IsWebSearchBaseUrlField(ToolDefinition definition, string fieldName) => + definition.Id.Equals(ToolSelectionRules.WEB_SEARCH_TOOL_ID, StringComparison.Ordinal) && + fieldName.Equals(WEB_SEARCH_BASE_URL_FIELD, StringComparison.Ordinal); + + private static bool IsWebSearchBaseUrlLocked() => + ManagedConfiguration.TryGet(x => x.Tools, x => x.WebSearchBaseUrl, out var meta) && meta.IsLocked; + private static bool IsReadWebPageAllowedPrivateHostsField(ToolDefinition definition, string fieldName) => definition.Id.Equals(ToolSelectionRules.READ_WEB_PAGE_TOOL_ID, StringComparison.Ordinal) && fieldName.Equals(READ_WEB_PAGE_ALLOWED_PRIVATE_HOSTS_FIELD, StringComparison.Ordinal);