diff --git a/assets/javascripts/discourse/admin/models/ai-tool.js b/assets/javascripts/discourse/admin/models/ai-tool.js
index 5002806e..8cf94f47 100644
--- a/assets/javascripts/discourse/admin/models/ai-tool.js
+++ b/assets/javascripts/discourse/admin/models/ai-tool.js
@@ -20,26 +20,25 @@ export default class AiTool extends RestModel {
return this.getProperties(CREATE_ATTRIBUTES);
}
- workingCopy() {
- const attrs = this.getProperties(CREATE_ATTRIBUTES);
-
- attrs.parameters = new TrackedArray(
- attrs.parameters?.map((p) => {
+ trackParameters(parameters) {
+ return new TrackedArray(
+ parameters?.map((p) => {
const parameter = new TrackedObject(p);
- //Backwards-compatibility code.
- // TODO(roman): Remove aug 2024. Leave only else clause.
- if (parameter.enum_values) {
- parameter.enum = new TrackedArray(parameter.enum_values);
- delete parameter.enum_values;
- } else {
+ if (parameter.enum && parameter.enum.length) {
parameter.enum = new TrackedArray(parameter.enum);
+ } else {
+ parameter.enum = null;
}
return parameter;
})
);
+ }
+ workingCopy() {
+ const attrs = this.getProperties(CREATE_ATTRIBUTES);
+ attrs.parameters = this.trackParameters(attrs.parameters);
return this.store.createRecord("ai-tool", attrs);
}
}
diff --git a/assets/javascripts/discourse/components/ai-tool-editor.gjs b/assets/javascripts/discourse/components/ai-tool-editor.gjs
index 7c700280..06f0b65f 100644
--- a/assets/javascripts/discourse/components/ai-tool-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-tool-editor.gjs
@@ -25,6 +25,7 @@ export default class AiToolEditor extends Component {
@service dialog;
@service modal;
@service toasts;
+ @service store;
@tracked isSaving = false;
@tracked editingModel = null;
@@ -53,8 +54,9 @@ export default class AiToolEditor extends Component {
@action
configurePreset() {
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
- this.editingModel = this.args.model.workingCopy();
- this.editingModel.setProperties(this.selectedPreset);
+ this.editingModel = this.store
+ .createRecord("ai-tool", this.selectedPreset)
+ .workingCopy();
this.showDelete = false;
}
diff --git a/assets/javascripts/discourse/components/ai-tool-parameter-editor.gjs b/assets/javascripts/discourse/components/ai-tool-parameter-editor.gjs
index dda4bde0..2e8e3ccc 100644
--- a/assets/javascripts/discourse/components/ai-tool-parameter-editor.gjs
+++ b/assets/javascripts/discourse/components/ai-tool-parameter-editor.gjs
@@ -7,7 +7,6 @@ import DButton from "discourse/components/d-button";
import withEventValue from "discourse/helpers/with-event-value";
import I18n from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
-import and from "truth-helpers/helpers/and";
const PARAMETER_TYPES = [
{ name: "string", id: "string" },
@@ -59,6 +58,9 @@ export default class AiToolParameterEditor extends Component {
@action
removeEnumValue(parameter, index) {
parameter.enum.splice(index, 1);
+ if (parameter.enum.length === 0) {
+ parameter.enum = null;
+ }
}
@action
@@ -94,6 +96,7 @@ export default class AiToolParameterEditor extends Component {
{{on "input" (fn this.toggleRequired parameter)}}
checked={{parameter.required}}
type="checkbox"
+ class="parameter-row__required-toggle"
/>
{{I18n.t "discourse_ai.tools.parameter_required"}}
@@ -101,8 +104,9 @@ export default class AiToolParameterEditor extends Component {
@@ -114,7 +118,7 @@ export default class AiToolParameterEditor extends Component {
/>
- {{#if (and parameter.enum parameter.enum.length)}}
+ {{#if parameter.enum}}
{{#each parameter.enum as |enumValue enumIndex|}}
diff --git a/lib/ai_bot/tools/search_settings.rb b/lib/ai_bot/tools/search_settings.rb
index c89acc0f..ad0af7fa 100644
--- a/lib/ai_bot/tools/search_settings.rb
+++ b/lib/ai_bot/tools/search_settings.rb
@@ -31,20 +31,35 @@ module DiscourseAi
parameters[:query].to_s
end
+ def all_settings
+ @all_settings ||= SiteSetting.all_settings
+ end
+
+ def all_settings=(settings)
+ # this is only used for testing
+ @all_settings = settings
+ end
+
def invoke
@last_num_results = 0
terms = query.split(",").map(&:strip).map(&:downcase).reject(&:blank?)
+ terms_regexes =
+ terms.map do |term|
+ regex_string = term.split(/[ _\.\|]/).map { |t| Regexp.escape(t) }.join(".*")
+ Regexp.new(regex_string, Regexp::IGNORECASE)
+ end
+
found =
- SiteSetting.all_settings.filter do |setting|
+ all_settings.filter do |setting|
name = setting[:setting].to_s.downcase
description = setting[:description].to_s.downcase
plugin = setting[:plugin].to_s.downcase
search_string = "#{name} #{description} #{plugin}"
- terms.any? { |term| search_string.include?(term) }
+ terms_regexes.any? { |regex| search_string.match?(regex) }
end
if found.blank?
diff --git a/spec/lib/modules/ai_bot/tools/search_settings_spec.rb b/spec/lib/modules/ai_bot/tools/search_settings_spec.rb
index 4b155986..0796e89a 100644
--- a/spec/lib/modules/ai_bot/tools/search_settings_spec.rb
+++ b/spec/lib/modules/ai_bot/tools/search_settings_spec.rb
@@ -5,13 +5,28 @@ RSpec.describe DiscourseAi::AiBot::Tools::SearchSettings do
let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) }
let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") }
+ let(:fake_settings) do
+ [
+ { setting: "default_locale", description: "The default locale for the site", plugin: "core" },
+ { setting: "min_post_length", description: "The minimum length of a post", plugin: "core" },
+ {
+ setting: "ai_bot_enabled",
+ description: "Enable or disable the AI bot",
+ plugin: "discourse-ai",
+ },
+ { setting: "min_first_post_length", description: "First post length", plugin: "core" },
+ ]
+ end
+
before do
SiteSetting.ai_bot_enabled = true
toggle_enabled_bots(bots: [llm_model])
end
- def search_settings(query)
- described_class.new({ query: query }, bot_user: bot_user, llm: llm)
+ def search_settings(query, mock: true)
+ search = described_class.new({ query: query }, bot_user: bot_user, llm: llm)
+ search.all_settings = fake_settings if mock
+ search
end
describe "#process" do
@@ -21,8 +36,20 @@ RSpec.describe DiscourseAi::AiBot::Tools::SearchSettings do
expect(results[:rows]).to eq([])
end
+ it "can find a setting based on fuzzy match" do
+ results = search_settings("default locale").invoke
+ expect(results[:rows].length).to eq(1)
+ expect(results[:rows][0][0]).to eq("default_locale")
+
+ results = search_settings("min_post_length").invoke
+
+ expect(results[:rows].length).to eq(2)
+ expect(results[:rows][0][0]).to eq("min_post_length")
+ expect(results[:rows][1][0]).to eq("min_first_post_length")
+ end
+
it "can return more many settings with no descriptions if there are lots of hits" do
- results = search_settings("a").invoke
+ results = search_settings("a", mock: false).invoke
expect(results[:rows].length).to be > 30
expect(results[:rows][0].length).to eq(1)
diff --git a/spec/system/ai_bot/tool_spec.rb b/spec/system/ai_bot/tool_spec.rb
index ec397831..4a97ed5c 100644
--- a/spec/system/ai_bot/tool_spec.rb
+++ b/spec/system/ai_bot/tool_spec.rb
@@ -22,6 +22,10 @@ describe "AI Tool Management", type: :system do
select_kit.select_row_by_value("exchange_rate")
find(".ai-tool-editor__next").click
+
+ expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
+ expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
+
find(".ai-tool-editor__test-button").click
expect(page).not_to have_button(".ai-tool-editor__delete")
@@ -49,6 +53,12 @@ describe "AI Tool Management", type: :system do
expect(page).to have_content("Tool saved")
+ last_tool = AiTool.order("id desc").limit(1).first
+ visit "/admin/plugins/discourse-ai/ai-tools/#{last_tool.id}"
+
+ expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
+ expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
+
visit "/admin/plugins/discourse-ai/ai-personas/new"
tool_id = AiTool.order("id desc").limit(1).pluck(:id).first