Skip to content

Commit 41054c4

Browse files
authored
FEATURE: improve site setting search (#780)
This improves the site setting search so it performs a somewhat fuzzy match. Previously it did not handle seperators such as "space" and a term such as "min_post_length" would not find "min_first_post_length" A more liberal search algorithm makes it easier to the AI to navigate settings. * Minor fix, {{and parameter.enum parameter.enum.length}} is non obviously broken. If parameter.enum is a tracked array it will return the object cause embers and helper implementation. This corrects an issue where enum keeps on selecting itself by mistake.
1 parent 9435040 commit 41054c4

File tree

6 files changed

+78
-21
lines changed

6 files changed

+78
-21
lines changed

assets/javascripts/discourse/admin/models/ai-tool.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,25 @@ export default class AiTool extends RestModel {
2020
return this.getProperties(CREATE_ATTRIBUTES);
2121
}
2222

23-
workingCopy() {
24-
const attrs = this.getProperties(CREATE_ATTRIBUTES);
25-
26-
attrs.parameters = new TrackedArray(
27-
attrs.parameters?.map((p) => {
23+
trackParameters(parameters) {
24+
return new TrackedArray(
25+
parameters?.map((p) => {
2826
const parameter = new TrackedObject(p);
2927

30-
//Backwards-compatibility code.
31-
// TODO(roman): Remove aug 2024. Leave only else clause.
32-
if (parameter.enum_values) {
33-
parameter.enum = new TrackedArray(parameter.enum_values);
34-
delete parameter.enum_values;
35-
} else {
28+
if (parameter.enum && parameter.enum.length) {
3629
parameter.enum = new TrackedArray(parameter.enum);
30+
} else {
31+
parameter.enum = null;
3732
}
3833

3934
return parameter;
4035
})
4136
);
37+
}
4238

39+
workingCopy() {
40+
const attrs = this.getProperties(CREATE_ATTRIBUTES);
41+
attrs.parameters = this.trackParameters(attrs.parameters);
4342
return this.store.createRecord("ai-tool", attrs);
4443
}
4544
}

assets/javascripts/discourse/components/ai-tool-editor.gjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default class AiToolEditor extends Component {
2525
@service dialog;
2626
@service modal;
2727
@service toasts;
28+
@service store;
2829

2930
@tracked isSaving = false;
3031
@tracked editingModel = null;
@@ -53,8 +54,9 @@ export default class AiToolEditor extends Component {
5354
@action
5455
configurePreset() {
5556
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
56-
this.editingModel = this.args.model.workingCopy();
57-
this.editingModel.setProperties(this.selectedPreset);
57+
this.editingModel = this.store
58+
.createRecord("ai-tool", this.selectedPreset)
59+
.workingCopy();
5860
this.showDelete = false;
5961
}
6062

assets/javascripts/discourse/components/ai-tool-parameter-editor.gjs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import DButton from "discourse/components/d-button";
77
import withEventValue from "discourse/helpers/with-event-value";
88
import I18n from "discourse-i18n";
99
import ComboBox from "select-kit/components/combo-box";
10-
import and from "truth-helpers/helpers/and";
1110

1211
const PARAMETER_TYPES = [
1312
{ name: "string", id: "string" },
@@ -59,6 +58,9 @@ export default class AiToolParameterEditor extends Component {
5958
@action
6059
removeEnumValue(parameter, index) {
6160
parameter.enum.splice(index, 1);
61+
if (parameter.enum.length === 0) {
62+
parameter.enum = null;
63+
}
6264
}
6365

6466
@action
@@ -94,15 +96,17 @@ export default class AiToolParameterEditor extends Component {
9496
{{on "input" (fn this.toggleRequired parameter)}}
9597
checked={{parameter.required}}
9698
type="checkbox"
99+
class="parameter-row__required-toggle"
97100
/>
98101
{{I18n.t "discourse_ai.tools.parameter_required"}}
99102
</label>
100103

101104
<label>
102105
<input
103106
{{on "input" (fn this.toggleEnum parameter)}}
104-
checked={{and parameter.enum parameter.enum.length}}
107+
checked={{parameter.enum}}
105108
type="checkbox"
109+
class="parameter-row__enum-toggle"
106110
/>
107111
{{I18n.t "discourse_ai.tools.parameter_enum"}}
108112
</label>
@@ -114,7 +118,7 @@ export default class AiToolParameterEditor extends Component {
114118
/>
115119
</div>
116120

117-
{{#if (and parameter.enum parameter.enum.length)}}
121+
{{#if parameter.enum}}
118122
<div class="parameter-enum-values">
119123
{{#each parameter.enum as |enumValue enumIndex|}}
120124
<div class="enum-value-row">

lib/ai_bot/tools/search_settings.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,35 @@ def query
3131
parameters[:query].to_s
3232
end
3333

34+
def all_settings
35+
@all_settings ||= SiteSetting.all_settings
36+
end
37+
38+
def all_settings=(settings)
39+
# this is only used for testing
40+
@all_settings = settings
41+
end
42+
3443
def invoke
3544
@last_num_results = 0
3645

3746
terms = query.split(",").map(&:strip).map(&:downcase).reject(&:blank?)
3847

48+
terms_regexes =
49+
terms.map do |term|
50+
regex_string = term.split(/[ _\.\|]/).map { |t| Regexp.escape(t) }.join(".*")
51+
Regexp.new(regex_string, Regexp::IGNORECASE)
52+
end
53+
3954
found =
40-
SiteSetting.all_settings.filter do |setting|
55+
all_settings.filter do |setting|
4156
name = setting[:setting].to_s.downcase
4257
description = setting[:description].to_s.downcase
4358
plugin = setting[:plugin].to_s.downcase
4459

4560
search_string = "#{name} #{description} #{plugin}"
4661

47-
terms.any? { |term| search_string.include?(term) }
62+
terms_regexes.any? { |regex| search_string.match?(regex) }
4863
end
4964

5065
if found.blank?

spec/lib/modules/ai_bot/tools/search_settings_spec.rb

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,28 @@
55
let(:bot_user) { DiscourseAi::AiBot::EntryPoint.find_user_from_model(llm_model.name) }
66
let(:llm) { DiscourseAi::Completions::Llm.proxy("custom:#{llm_model.id}") }
77

8+
let(:fake_settings) do
9+
[
10+
{ setting: "default_locale", description: "The default locale for the site", plugin: "core" },
11+
{ setting: "min_post_length", description: "The minimum length of a post", plugin: "core" },
12+
{
13+
setting: "ai_bot_enabled",
14+
description: "Enable or disable the AI bot",
15+
plugin: "discourse-ai",
16+
},
17+
{ setting: "min_first_post_length", description: "First post length", plugin: "core" },
18+
]
19+
end
20+
821
before do
922
SiteSetting.ai_bot_enabled = true
1023
toggle_enabled_bots(bots: [llm_model])
1124
end
1225

13-
def search_settings(query)
14-
described_class.new({ query: query }, bot_user: bot_user, llm: llm)
26+
def search_settings(query, mock: true)
27+
search = described_class.new({ query: query }, bot_user: bot_user, llm: llm)
28+
search.all_settings = fake_settings if mock
29+
search
1530
end
1631

1732
describe "#process" do
@@ -21,8 +36,20 @@ def search_settings(query)
2136
expect(results[:rows]).to eq([])
2237
end
2338

39+
it "can find a setting based on fuzzy match" do
40+
results = search_settings("default locale").invoke
41+
expect(results[:rows].length).to eq(1)
42+
expect(results[:rows][0][0]).to eq("default_locale")
43+
44+
results = search_settings("min_post_length").invoke
45+
46+
expect(results[:rows].length).to eq(2)
47+
expect(results[:rows][0][0]).to eq("min_post_length")
48+
expect(results[:rows][1][0]).to eq("min_first_post_length")
49+
end
50+
2451
it "can return more many settings with no descriptions if there are lots of hits" do
25-
results = search_settings("a").invoke
52+
results = search_settings("a", mock: false).invoke
2653

2754
expect(results[:rows].length).to be > 30
2855
expect(results[:rows][0].length).to eq(1)

spec/system/ai_bot/tool_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
select_kit.select_row_by_value("exchange_rate")
2323

2424
find(".ai-tool-editor__next").click
25+
26+
expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
27+
expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
28+
2529
find(".ai-tool-editor__test-button").click
2630

2731
expect(page).not_to have_button(".ai-tool-editor__delete")
@@ -49,6 +53,12 @@
4953

5054
expect(page).to have_content("Tool saved")
5155

56+
last_tool = AiTool.order("id desc").limit(1).first
57+
visit "/admin/plugins/discourse-ai/ai-tools/#{last_tool.id}"
58+
59+
expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
60+
expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
61+
5262
visit "/admin/plugins/discourse-ai/ai-personas/new"
5363

5464
tool_id = AiTool.order("id desc").limit(1).pluck(:id).first

0 commit comments

Comments
 (0)