DEV: migrates tools form to form-kit (#1204)
This PR is a retry of: #1135, where we migrate AiTools form to FormKit. The previous PR accidentally removed code related to setting enum values, and as a result was reverted. This update includes enums correctly along with the previous updates.
This commit is contained in:
+5
-1
@@ -1,6 +1,10 @@
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
||||
beforeModel(transition) {
|
||||
this.preset = transition.to.queryParams.presetId || "empty_tool";
|
||||
}
|
||||
|
||||
async model() {
|
||||
return this.store.createRecord("ai-tool");
|
||||
}
|
||||
@@ -8,10 +12,10 @@ export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
||||
|
||||
controller.set("allTools", toolsModel);
|
||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
||||
controller.set("llms", toolsModel.resultSetMeta.llms);
|
||||
controller.set("settings", toolsModel.resultSetMeta.settings);
|
||||
controller.set("selectedPreset", this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -5,5 +5,6 @@
|
||||
@presets={{this.presets}}
|
||||
@llms={{this.llms}}
|
||||
@settings={{this.settings}}
|
||||
@selectedPreset={{this.selectedPreset}}
|
||||
/>
|
||||
</section>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,36 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { fn } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { service } from "@ember/service";
|
||||
import { eq } from "truth-helpers";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
|
||||
export default class AiToolListEditor extends Component {
|
||||
@service adminPluginNavManager;
|
||||
@service router;
|
||||
|
||||
get lastIndexOfPresets() {
|
||||
return this.args.tools.resultSetMeta.presets.length - 1;
|
||||
}
|
||||
|
||||
@action
|
||||
routeToNewTool(preset) {
|
||||
return this.router.transitionTo(
|
||||
"adminPlugins.show.discourse-ai-tools.new",
|
||||
{
|
||||
queryParams: {
|
||||
presetId: preset.preset_id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
<DBreadcrumbsItem
|
||||
@@ -20,13 +43,33 @@ export default class AiToolListEditor extends Component {
|
||||
@learnMoreUrl="https://meta.discourse.org/t/ai-bot-custom-tools/314103"
|
||||
@descriptionLabel={{i18n "discourse_ai.tools.subheader_description"}}
|
||||
>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary
|
||||
@label="discourse_ai.tools.new"
|
||||
@route="adminPlugins.show.discourse-ai-tools.new"
|
||||
<:actions>
|
||||
<DMenu
|
||||
@triggerClass="btn-primary btn-small ai-tool-list-editor__new-button"
|
||||
@label={{i18n "discourse_ai.tools.new"}}
|
||||
@icon="plus"
|
||||
class="ai-tool-list-editor__new-button"
|
||||
/>
|
||||
@placement="bottom-end"
|
||||
>
|
||||
<:content>
|
||||
<DropdownMenu as |dropdown|>
|
||||
{{#each @tools.resultSetMeta.presets as |preset index|}}
|
||||
{{#if (eq index this.lastIndexOfPresets)}}
|
||||
<dropdown.divider />
|
||||
{{/if}}
|
||||
|
||||
<dropdown.item>
|
||||
<DButton
|
||||
@translatedLabel={{preset.preset_name}}
|
||||
@action={{fn this.routeToNewTool preset}}
|
||||
class="btn-transparent"
|
||||
data-option={{preset.preset_id}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
</DropdownMenu>
|
||||
|
||||
</:content>
|
||||
</DMenu>
|
||||
</:actions>
|
||||
</DPageSubheader>
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { fn } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
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";
|
||||
|
||||
const PARAMETER_TYPES = [
|
||||
{ name: "string", id: "string" },
|
||||
{ name: "number", id: "number" },
|
||||
{ name: "boolean", id: "boolean" },
|
||||
{ name: "array", id: "array" },
|
||||
];
|
||||
|
||||
export default class AiToolParameterEditor extends Component {
|
||||
@action
|
||||
addParameter() {
|
||||
this.args.parameters.push(
|
||||
new TrackedObject({
|
||||
name: "",
|
||||
description: "",
|
||||
type: "string",
|
||||
required: false,
|
||||
enum: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
removeParameter(parameter) {
|
||||
const index = this.args.parameters.indexOf(parameter);
|
||||
this.args.parameters.splice(index, 1);
|
||||
}
|
||||
|
||||
@action
|
||||
toggleRequired(parameter, event) {
|
||||
parameter.required = event.target.checked;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleEnum(parameter) {
|
||||
if (parameter.enum) {
|
||||
parameter.enum = null;
|
||||
} else {
|
||||
this.addEnumValue(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addEnumValue(parameter) {
|
||||
parameter.enum ||= new TrackedArray();
|
||||
parameter.enum.push("");
|
||||
}
|
||||
|
||||
@action
|
||||
removeEnumValue(parameter, index) {
|
||||
parameter.enum.splice(index, 1);
|
||||
if (parameter.enum.length === 0) {
|
||||
parameter.enum = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateEnumValue(parameter, index, event) {
|
||||
parameter.enum[index] = event.target.value;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#each @parameters as |parameter|}}
|
||||
<div class="ai-tool-parameter">
|
||||
<div class="parameter-row">
|
||||
<input
|
||||
{{on "input" (withEventValue (fn (mut parameter.name)))}}
|
||||
value={{parameter.name}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.parameter_name"}}
|
||||
/>
|
||||
<ComboBox @value={{parameter.type}} @content={{PARAMETER_TYPES}} />
|
||||
</div>
|
||||
|
||||
<div class="parameter-row">
|
||||
<input
|
||||
{{on "input" (withEventValue (fn (mut parameter.description)))}}
|
||||
value={{parameter.description}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.parameter_description"}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="parameter-row">
|
||||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleRequired parameter)}}
|
||||
checked={{parameter.required}}
|
||||
type="checkbox"
|
||||
class="parameter-row__required-toggle"
|
||||
/>
|
||||
{{i18n "discourse_ai.tools.parameter_required"}}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleEnum parameter)}}
|
||||
checked={{parameter.enum}}
|
||||
type="checkbox"
|
||||
class="parameter-row__enum-toggle"
|
||||
/>
|
||||
{{i18n "discourse_ai.tools.parameter_enum"}}
|
||||
</label>
|
||||
|
||||
<DButton
|
||||
@action={{fn this.removeParameter parameter}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if parameter.enum}}
|
||||
<div class="parameter-enum-values">
|
||||
{{#each parameter.enum as |enumValue enumIndex|}}
|
||||
<div class="enum-value-row">
|
||||
<input
|
||||
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}
|
||||
value={{enumValue}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.enum_value"}}
|
||||
/>
|
||||
<DButton
|
||||
@action={{fn this.removeEnumValue parameter enumIndex}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<DButton
|
||||
@action={{fn this.addEnumValue parameter}}
|
||||
@label="discourse_ai.tools.add_enum_value"
|
||||
@icon="plus"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<DButton
|
||||
@action={{this.addParameter}}
|
||||
@label="discourse_ai.tools.add_parameter"
|
||||
@icon="plus"
|
||||
/>
|
||||
</template>
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import DTooltip from "discourse/components/d-tooltip";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AiLlmSelector from "./ai-llm-selector";
|
||||
|
||||
export default class RagOptions extends Component {
|
||||
@tracked showIndexingOptions = false;
|
||||
|
||||
@action
|
||||
toggleIndexingOptions(event) {
|
||||
this.showIndexingOptions = !this.showIndexingOptions;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
get indexingOptionsText() {
|
||||
return this.showIndexingOptions
|
||||
? i18n("discourse_ai.rag.options.hide_indexing_options")
|
||||
: i18n("discourse_ai.rag.options.show_indexing_options");
|
||||
}
|
||||
|
||||
get visionLlms() {
|
||||
return this.args.llms.filter((llm) => llm.vision_enabled);
|
||||
}
|
||||
|
||||
get visionLlmId() {
|
||||
return this.args.model.rag_llm_model_id ?? "blank";
|
||||
}
|
||||
|
||||
set visionLlmId(value) {
|
||||
if (value === "blank") {
|
||||
this.args.model.rag_llm_model_id = null;
|
||||
} else {
|
||||
this.args.model.rag_llm_model_id = value;
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @model.rag_uploads}}
|
||||
<a
|
||||
href="#"
|
||||
class="rag-options__indexing-options"
|
||||
{{on "click" this.toggleIndexingOptions}}
|
||||
>{{this.indexingOptionsText}}</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showIndexingOptions}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.rag.options.rag_chunk_tokens"}}</label>
|
||||
<Input
|
||||
@type="number"
|
||||
step="any"
|
||||
lang="en"
|
||||
class="rag-options__rag_chunk_tokens"
|
||||
@value={{@model.rag_chunk_tokens}}
|
||||
/>
|
||||
<DTooltip
|
||||
@icon="circle-question"
|
||||
@content={{i18n "discourse_ai.rag.options.rag_chunk_tokens_help"}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>{{i18n
|
||||
"discourse_ai.rag.options.rag_chunk_overlap_tokens"
|
||||
}}</label>
|
||||
<Input
|
||||
@type="number"
|
||||
step="any"
|
||||
lang="en"
|
||||
class="rag-options__rag_chunk_overlap_tokens"
|
||||
@value={{@model.rag_chunk_overlap_tokens}}
|
||||
/>
|
||||
<DTooltip
|
||||
@icon="circle-question"
|
||||
@content={{i18n
|
||||
"discourse_ai.rag.options.rag_chunk_overlap_tokens_help"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{{#if @allowImages}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.rag.options.rag_llm_model"}}</label>
|
||||
<AiLlmSelector
|
||||
class="ai-persona-editor__llms"
|
||||
@value={{this.visionLlmId}}
|
||||
@llms={{this.visionLlms}}
|
||||
/>
|
||||
<DTooltip
|
||||
@icon="circle-question"
|
||||
@content={{i18n "discourse_ai.rag.options.rag_llm_model_help"}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{yield}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
@@ -135,7 +135,10 @@ export default class RagUploader extends Component {
|
||||
{{#if this.ragUploads}}
|
||||
<div class="rag-uploader__search-input-container">
|
||||
<div class="rag-uploader__search-input">
|
||||
{{icon "search" class="rag-uploader__search-input__search-icon"}}
|
||||
{{icon
|
||||
"magnifying-glass"
|
||||
class="rag-uploader__search-input__search-icon"
|
||||
}}
|
||||
<Input
|
||||
class="rag-uploader__search-input__input"
|
||||
placeholder={{i18n "discourse_ai.rag.uploads.filter"}}
|
||||
|
||||
@@ -104,7 +104,9 @@
|
||||
}
|
||||
|
||||
&__uploads-list {
|
||||
margin-bottom: 20px;
|
||||
&:has(tr) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
tbody {
|
||||
border-top: none;
|
||||
@@ -133,4 +135,9 @@
|
||||
&__rag-file-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.hidden-upload-field {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,27 @@
|
||||
.ai-tool-parameter {
|
||||
margin-bottom: 2em;
|
||||
padding: 1.5em;
|
||||
border: 1px solid var(--primary-low);
|
||||
border-radius: 3px;
|
||||
background-color: var(--secondary-very-low);
|
||||
border: 1px solid var(--primary-low-mid);
|
||||
border-radius: var(--d-input-border-radius);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.parameter-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
|
||||
input[type="text"] {
|
||||
flex-grow: 1;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 1em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.form-kit__container-content {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.parameter-enum-values {
|
||||
margin-top: 0.5em;
|
||||
.ai-tool-parameter__enum-values {
|
||||
margin-block: 1rem;
|
||||
|
||||
.enum-value-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5em;
|
||||
.form-kit__container-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
position: relative;
|
||||
|
||||
input[type="text"] {
|
||||
flex-grow: 1;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.form-kit__button.btn-icon-text {
|
||||
justify-self: start;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,20 +30,10 @@
|
||||
max-width: 80%;
|
||||
position: relative;
|
||||
|
||||
.ace-wrapper {
|
||||
border: 1px solid var(--primary-low);
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
|
||||
.ace_editor {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
#control-rag_uploads .rag-uploader {
|
||||
h3,
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +371,9 @@ en:
|
||||
script: "Script"
|
||||
parameters: "Parameters"
|
||||
save: "Save"
|
||||
add_parameter: "Add Parameter"
|
||||
parameter_type: "Parameter type"
|
||||
add_parameter: "Add parameter"
|
||||
remove_parameter: "Remove"
|
||||
parameter_required: "Required"
|
||||
parameter_enum: "Enum"
|
||||
parameter_name: "Parameter name"
|
||||
@@ -382,10 +384,7 @@ en:
|
||||
test: "Run test"
|
||||
delete: "Delete"
|
||||
saved: "Tool saved"
|
||||
presets: "Select a preset..."
|
||||
confirm_delete: "Are you sure you want to delete this tool?"
|
||||
next:
|
||||
title: "Next"
|
||||
test_modal:
|
||||
title: "Test AI tool"
|
||||
run: "Run test"
|
||||
|
||||
@@ -36,21 +36,18 @@ describe "AI Tool Management", type: :system do
|
||||
|
||||
it "allows admin to create a new AI tool from preset" do
|
||||
visit "/admin/plugins/discourse-ai/ai-tools"
|
||||
|
||||
expect(page_header).to be_visible
|
||||
expect(page).to have_content("Tools")
|
||||
|
||||
find(".ai-tool-list-editor__new-button").click
|
||||
expect(page_header).to be_hidden
|
||||
|
||||
select_kit = PageObjects::Components::SelectKit.new(".ai-tool-editor__presets")
|
||||
select_kit.expand
|
||||
select_kit.select_row_by_value("exchange_rate")
|
||||
tool_presets = PageObjects::Components::DMenu.new(find(".ai-tool-list-editor__new-button"))
|
||||
tool_presets.option(".btn[data-option='exchange_rate'").click
|
||||
|
||||
find(".ai-tool-editor__next").click
|
||||
required_toggle_css = "#control-parameters-0-required .form-kit__control-checkbox"
|
||||
enum_toggle_css = "#control-parameters-0-isEnum .form-kit__control-checkbox"
|
||||
|
||||
expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
|
||||
expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
|
||||
expect(page.find(required_toggle_css).checked?).to eq(true)
|
||||
expect(page.find(enum_toggle_css).checked?).to eq(false)
|
||||
|
||||
# not allowed to test yet
|
||||
expect(page).not_to have_button(".ai-tool-editor__test-button")
|
||||
@@ -65,8 +62,8 @@ describe "AI Tool Management", type: :system do
|
||||
|
||||
ensure_can_run_test
|
||||
|
||||
expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
|
||||
expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
|
||||
expect(page.first(required_toggle_css).checked?).to eq(true)
|
||||
expect(page.first(enum_toggle_css).checked?).to eq(false)
|
||||
|
||||
visit "/admin/plugins/discourse-ai/ai-personas/new"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user