discourse-ai/assets/javascripts/discourse/components/ai-tool-editor.gjs
Sam 12869f2146
FIX: testing tool was not showing rag results (#867)
This changeset contains 4 fixes:

1. We were allowing running tests on unsaved tools,
this is problematic cause uploads are not yet associated or indexed
leading to confusing results. We now only show the test button when
tool is saved.


2. We were not properly scoping rag document fragements, this
meant that personas and ai tools could get results from other
unrelated tools, just to be filtered out later


3. index.search showed options as "optional" but implementation
required the second option

4. When testing tools searching through document fragments was
not working at all cause we did not properly load the tool
2024-10-25 16:01:25 +11:00

261 lines
7.4 KiB
Text

import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { service } from "@ember/service";
import AceEditor from "discourse/components/ace-editor";
import BackButton from "discourse/components/back-button";
import DButton from "discourse/components/d-button";
import DTooltip from "discourse/components/d-tooltip";
import withEventValue from "discourse/helpers/with-event-value";
import { popupAjaxError } from "discourse/lib/ajax-error";
import I18n from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
import AiToolParameterEditor from "./ai-tool-parameter-editor";
import AiToolTestModal from "./modal/ai-tool-test-modal";
import RagOptions from "./rag-options";
import RagUploader from "./rag-uploader";
const ACE_EDITOR_MODE = "javascript";
const ACE_EDITOR_THEME = "chrome";
export default class AiToolEditor extends Component {
@service router;
@service dialog;
@service modal;
@service toasts;
@service store;
@service siteSettings;
@tracked isSaving = false;
@tracked editingModel = null;
@tracked showDelete = false;
@tracked selectedPreset = null;
get presets() {
return this.args.presets.map((preset) => {
return {
name: preset.preset_name,
id: preset.preset_id,
};
});
}
get showPresets() {
return !this.selectedPreset && this.args.model.isNew;
}
@action
updateModel() {
this.editingModel = this.args.model.workingCopy();
this.showDelete = !this.args.model.isNew;
}
@action
configurePreset() {
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
this.editingModel = this.store
.createRecord("ai-tool", this.selectedPreset)
.workingCopy();
this.showDelete = false;
}
@action
updateUploads(uploads) {
this.editingModel.rag_uploads = uploads;
}
@action
removeUpload(upload) {
this.editingModel.rag_uploads.removeObject(upload);
if (!this.args.model.isNew) {
this.save();
}
}
@action
async save() {
this.isSaving = true;
try {
const data = this.editingModel.getProperties(
"name",
"description",
"parameters",
"script",
"summary",
"rag_uploads",
"rag_chunk_tokens",
"rag_chunk_overlap_tokens"
);
await this.args.model.save(data);
this.toasts.success({
data: { message: I18n.t("discourse_ai.tools.saved") },
duration: 2000,
});
if (!this.args.tools.any((tool) => tool.id === this.args.model.id)) {
this.args.tools.pushObject(this.args.model);
}
this.router.transitionTo(
"adminPlugins.show.discourse-ai-tools.show",
this.args.model
);
} catch (e) {
popupAjaxError(e);
} finally {
this.isSaving = false;
}
}
@action
delete() {
return this.dialog.confirm({
message: I18n.t("discourse_ai.tools.confirm_delete"),
didConfirm: async () => {
await this.args.model.destroyRecord();
this.args.tools.removeObject(this.args.model);
this.router.transitionTo("adminPlugins.show.discourse-ai-tools.index");
},
});
}
@action
openTestModal() {
this.modal.show(AiToolTestModal, {
model: {
tool: this.editingModel,
},
});
}
<template>
<BackButton
@route="adminPlugins.show.discourse-ai-tools"
@label="discourse_ai.tools.back"
/>
<form
{{didInsert this.updateModel @model.id}}
{{didUpdate this.updateModel @model.id}}
class="form-horizontal ai-tool-editor"
>
{{#if this.showPresets}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.presets"}}</label>
<ComboBox
@value={{this.presetId}}
@content={{this.presets}}
class="ai-tool-editor__presets"
/>
</div>
<div class="control-group ai-llm-editor__action_panel">
<DButton
@action={{this.configurePreset}}
@label="discourse_ai.tools.next.title"
class="ai-tool-editor__next"
/>
</div>
{{else}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.name"}}</label>
<input
{{on "input" (withEventValue (fn (mut this.editingModel.name)))}}
value={{this.editingModel.name}}
type="text"
class="ai-tool-editor__name"
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.tools.name_help"}}
/>
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.description"}}</label>
<textarea
{{on
"input"
(withEventValue (fn (mut this.editingModel.description)))
}}
placeholder={{I18n.t "discourse_ai.tools.description_help"}}
class="ai-tool-editor__description input-xxlarge"
>{{this.editingModel.description}}</textarea>
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.summary"}}</label>
<input
{{on "input" (withEventValue (fn (mut this.editingModel.summary)))}}
value={{this.editingModel.summary}}
type="text"
class="ai-tool-editor__summary input-xxlarge"
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.tools.summary_help"}}
/>
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.parameters"}}</label>
<AiToolParameterEditor @parameters={{this.editingModel.parameters}} />
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.tools.script"}}</label>
<AceEditor
@content={{this.editingModel.script}}
@onChange={{fn (mut this.editingModel.script)}}
@mode={{ACE_EDITOR_MODE}}
@theme={{ACE_EDITOR_THEME}}
@editorId="ai-tool-script-editor"
/>
</div>
{{#if this.siteSettings.ai_embeddings_enabled}}
<div class="control-group">
<RagUploader
@target={{this.editingModel}}
@updateUploads={{this.updateUploads}}
@onRemove={{this.removeUpload}}
/>
</div>
<RagOptions @model={{this.editingModel}} />
{{/if}}
<div class="control-group ai-tool-editor__action_panel">
{{#unless @model.isNew}}
<DButton
@action={{this.openTestModal}}
@label="discourse_ai.tools.test"
class="ai-tool-editor__test-button"
/>
{{/unless}}
<DButton
@action={{this.save}}
@label="discourse_ai.tools.save"
@disabled={{this.isSaving}}
class="btn-primary ai-tool-editor__save"
/>
{{#if this.showDelete}}
<DButton
@action={{this.delete}}
@label="discourse_ai.tools.delete"
class="btn-danger ai-tool-editor__delete"
/>
{{/if}}
</div>
{{/if}}
</form>
</template>
}