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.
This commit is contained in:
parent
943504049c
commit
41054c4fb8
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"}}
|
||||
</label>
|
||||
|
@ -101,8 +104,9 @@ export default class AiToolParameterEditor extends Component {
|
|||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleEnum parameter)}}
|
||||
checked={{and parameter.enum parameter.enum.length}}
|
||||
checked={{parameter.enum}}
|
||||
type="checkbox"
|
||||
class="parameter-row__enum-toggle"
|
||||
/>
|
||||
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
||||
</label>
|
||||
|
@ -114,7 +118,7 @@ export default class AiToolParameterEditor extends Component {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{{#if (and parameter.enum parameter.enum.length)}}
|
||||
{{#if parameter.enum}}
|
||||
<div class="parameter-enum-values">
|
||||
{{#each parameter.enum as |enumValue enumIndex|}}
|
||||
<div class="enum-value-row">
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue