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