Revert "DEV: Convert tool editor to form kit (#1135)" (#1201)

This reverts commit 107f14456b0e4b51fd2b934ee7cceced78b2e0cc.

enum was not handled, so reverting for now
This commit is contained in:
Sam 2025-03-18 18:07:04 +11:00 committed by GitHub
parent 5bf61ef9e1
commit b6483e416d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 491 additions and 418 deletions

View File

@ -1,10 +1,6 @@
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");
}
@ -12,10 +8,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);
}
}

View File

@ -5,6 +5,5 @@
@presets={{this.presets}}
@llms={{this.llms}}
@settings={{this.settings}}
@selectedPreset={{this.selectedPreset}}
/>
</section>

View File

@ -1,317 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { fn, hash } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import Form from "discourse/components/form";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { i18n } from "discourse-i18n";
import AiToolTestModal from "./modal/ai-tool-test-modal";
import RagOptions from "./rag-options";
import RagUploader from "./rag-uploader";
export default class AiToolEditorForm extends Component {
@service modal;
@service siteSettings;
@service dialog;
@service router;
@service toasts;
@tracked uploadedFiles = [];
@tracked isSaving = false;
PARAMETER_TYPES = [
{ name: "string", id: "string" },
{ name: "number", id: "number" },
{ name: "boolean", id: "boolean" },
{ name: "array", id: "array" },
];
get formData() {
return {
name: this.args.editingModel.name || "",
tool_name: this.args.editingModel.tool_name || "",
description: this.args.editingModel.description || "",
summary: this.args.editingModel.summary || "",
parameters: this.args.editingModel.parameters || [],
script: this.args.editingModel.script || "",
rag_uploads: this.args.editingModel.rag_uploads || [],
};
}
@action
async save(data) {
this.isSaving = true;
try {
await this.args.model.save(data);
this.toasts.success({
data: { message: i18n("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.edit",
this.args.model
);
} catch (e) {
popupAjaxError(e);
} finally {
this.isSaving = false;
}
}
@action
delete() {
return this.dialog.confirm({
message: i18n("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
updateUploads(addItemToCollection, uploads) {
const uniqueUploads = uploads.filter(
(upload) => !this.uploadedFiles.some((file) => file.id === upload.id)
);
addItemToCollection("rag_uploads", uniqueUploads);
this.uploadedFiles = [...this.uploadedFiles, ...uniqueUploads];
}
@action
removeUpload(form, upload) {
this.uploadedFiles = this.uploadedFiles.filter(
(file) => file.id !== upload.id
);
form.set("rag_uploads", this.uploadedFiles);
}
@action
openTestModal() {
this.modal.show(AiToolTestModal, {
model: {
tool: this.args.editingModel,
},
});
}
currentParameterSelection(data, index) {
return data.parameters[index].type;
}
get ragUploadsDescription() {
return this.siteSettings.rag_images_enabled
? i18n("discourse_ai.rag.uploads.description_with_images")
: i18n("discourse_ai.rag.uploads.description");
}
<template>
<Form
@onSubmit={{this.save}}
@data={{this.formData}}
class="ai-tool-editor"
as |form data|
>
{{! NAME }}
<form.Field
@name="name"
@title={{i18n "discourse_ai.tools.name"}}
@validation="required|length:1,100"
@format="large"
@tooltip={{i18n "discourse_ai.tools.name_help"}}
as |field|
>
<field.Input class="ai-tool-editor__name" />
</form.Field>
{{! TOOL NAME }}
<form.Field
@name="tool_name"
@title={{i18n "discourse_ai.tools.tool_name"}}
@validation="required|length:1,100"
@format="large"
@tooltip={{i18n "discourse_ai.tools.tool_name_help"}}
as |field|
>
<field.Input class="ai-tool-editor__tool_name" />
</form.Field>
{{! DESCRIPTION }}
<form.Field
@name="description"
@title={{i18n "discourse_ai.tools.description"}}
@validation="required|length:1,1000"
@format="full"
@tooltip={{i18n "discourse_ai.tools.description_help"}}
as |field|
>
<field.Textarea
@height={{60}}
class="ai-tool-editor__description"
placeholder={{i18n "discourse_ai.tools.description_help"}}
/>
</form.Field>
{{! SUMMARY }}
<form.Field
@name="summary"
@title={{i18n "discourse_ai.tools.summary"}}
@validation="required|length:1,255"
@format="large"
@tooltip={{i18n "discourse_ai.tools.summary_help"}}
as |field|
>
<field.Input class="ai-tool-editor__summary" />
</form.Field>
{{! PARAMETERS }}
<form.Collection @name="parameters" as |collection index|>
<div class="ai-tool-parameter">
<form.Row as |row|>
<row.Col @size={{6}}>
<collection.Field
@name="name"
@title={{i18n "discourse_ai.tools.parameter_name"}}
@validation="required|length:1,100"
as |field|
>
<field.Input />
</collection.Field>
</row.Col>
<row.Col @size={{6}}>
<collection.Field
@name="type"
@title={{i18n "discourse_ai.tools.parameter_type"}}
@validation="required"
as |field|
>
<field.Menu
@selection={{this.currentParameterSelection data index}}
as |menu|
>
{{#each this.PARAMETER_TYPES as |type|}}
<menu.Item
@value={{type.id}}
data-type={{type.id}}
>{{type.name}}</menu.Item>
{{/each}}
</field.Menu>
</collection.Field>
</row.Col>
</form.Row>
<collection.Field
@name="description"
@title={{i18n "discourse_ai.tools.parameter_description"}}
@validation="required|length:1,1000"
as |field|
>
<field.Input class="ai-tool-editor__parameter-description" />
</collection.Field>
<form.Row as |row|>
<row.Col @size={{4}}>
<collection.Field @name="required" @title="Required" as |field|>
<field.Checkbox />
</collection.Field>
</row.Col>
<row.Col @size={{4}}>
<collection.Field @name="enum" @title="Enum" as |field|>
<field.Checkbox />
</collection.Field>
</row.Col>
<row.Col @size={{4}} class="ai-tool-parameter-actions">
<form.Button
@label="discourse_ai.tools.remove_parameter"
@icon="trash-can"
@action={{fn collection.remove index}}
class="btn-danger"
/>
</row.Col>
</form.Row>
</div>
</form.Collection>
<form.Button
@icon="plus"
@label="discourse_ai.tools.add_parameter"
@action={{fn
form.addItemToCollection
"parameters"
(hash name="" type="string" description="" required=false enum=false)
}}
/>
{{! SCRIPT }}
<form.Field
@name="script"
@title={{i18n "discourse_ai.tools.script"}}
@validation="required|length:1,100000"
@format="full"
as |field|
>
<field.Code @lang="javascript" @height={{600}} />
</form.Field>
{{! Uploads }}
{{#if this.siteSettings.ai_embeddings_enabled}}
<form.Field
@name="rag_uploads"
@title={{i18n "discourse_ai.rag.uploads.title"}}
@tooltip={{this.ragUploadsDescription}}
as |field|
>
<field.Custom>
<RagUploader
@target={{@editingModel}}
@updateUploads={{fn this.updateUploads form.addItemToCollection}}
@onRemove={{fn this.removeUpload form}}
@allowImages={{@settings.rag_images_enabled}}
/>
<RagOptions
@model={{@editingModel}}
@llms={{@llms}}
@allowImages={{@settings.rag_images_enabled}}
/>
</field.Custom>
</form.Field>
{{/if}}
<form.Actions>
{{#unless @isNew}}
<form.Button
@label="discourse_ai.tools.test"
@action={{this.openTestModal}}
class="ai-tool-editor__test-button"
/>
<form.Button
@label="discourse_ai.tools.delete"
@icon="trash-can"
@action={{this.delete}}
class="btn-danger ai-tool-editor__delete"
/>
{{/unless}}
<form.Submit
@label="discourse_ai.tools.save"
class="ai-tool-editor__save"
/>
</form.Actions>
</Form>
</template>
}

View File

@ -1,39 +1,285 @@
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 AiToolEditorForm from "./ai-tool-editor-form";
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;
get selectedPreset() {
if (!this.args.selectedPreset) {
return this.args.presets.findBy("preset_id", "empty_tool");
}
@tracked isSaving = false;
@tracked editingModel = null;
@tracked showDelete = false;
@tracked selectedPreset = null;
return this.args.presets.findBy("preset_id", this.args.selectedPreset);
get presets() {
return this.args.presets.map((preset) => {
return {
name: preset.preset_name,
id: preset.preset_id,
};
});
}
get editingModel() {
if (this.args.model.isNew) {
return this.store.createRecord("ai-tool", this.selectedPreset);
} else {
return this.args.model;
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",
"tool_name",
"description",
"parameters",
"script",
"summary",
"rag_uploads",
"rag_chunk_tokens",
"rag_chunk_overlap_tokens",
"rag_llm_model_id"
);
await this.args.model.save(data);
this.toasts.success({
data: { message: i18n("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.edit",
this.args.model
);
} catch (e) {
popupAjaxError(e);
} finally {
this.isSaving = false;
}
}
@action
delete() {
return this.dialog.confirm({
message: i18n("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"
/>
<AiToolEditorForm
@model={{@model}}
@tools={{@tools}}
@editingModel={{this.editingModel}}
@isNew={{@model.isNew}}
@selectedPreset={{this.selectedPreset}}
/>
<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 "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 "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="circle-question"
@content={{i18n "discourse_ai.tools.name_help"}}
/>
</div>
<div class="control-group">
<label>{{i18n "discourse_ai.tools.tool_name"}}</label>
<input
{{on
"input"
(withEventValue (fn (mut this.editingModel.tool_name)))
}}
value={{this.editingModel.tool_name}}
type="text"
class="ai-tool-editor__tool_name"
/>
<DTooltip
@icon="circle-question"
@content={{i18n "discourse_ai.tools.tool_name_help"}}
/>
</div>
<div class="control-group">
<label>{{i18n "discourse_ai.tools.description"}}</label>
<textarea
{{on
"input"
(withEventValue (fn (mut this.editingModel.description)))
}}
placeholder={{i18n "discourse_ai.tools.description_help"}}
class="ai-tool-editor__description input-xxlarge"
>{{this.editingModel.description}}</textarea>
</div>
<div class="control-group">
<label>{{i18n "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="circle-question"
@content={{i18n "discourse_ai.tools.summary_help"}}
/>
</div>
<div class="control-group">
<label>{{i18n "discourse_ai.tools.parameters"}}</label>
<AiToolParameterEditor @parameters={{this.editingModel.parameters}} />
</div>
<div class="control-group">
<label>{{i18n "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}}
@allowImages={{@settings.rag_images_enabled}}
/>
</div>
<RagOptions
@model={{this.editingModel}}
@llms={{@llms}}
@allowImages={{@settings.rag_images_enabled}}
/>
{{/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>
}

View File

@ -1,36 +1,13 @@
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
@ -43,33 +20,13 @@ 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>
<DMenu
@triggerClass="btn-primary btn-small ai-tool-list-editor__new-button"
@label={{i18n "discourse_ai.tools.new"}}
<:actions as |actions|>
<actions.Primary
@label="discourse_ai.tools.new"
@route="adminPlugins.show.discourse-ai-tools.new"
@icon="plus"
@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>
class="ai-tool-list-editor__new-button"
/>
</:actions>
</DPageSubheader>

View File

@ -0,0 +1,155 @@
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>
}

View File

@ -137,10 +137,7 @@ export default class RagUploader extends Component {
{{#if this.ragUploads}}
<div class="rag-uploader__search-input-container">
<div class="rag-uploader__search-input">
{{icon
"magnifying-glass"
class="rag-uploader__search-input__search-icon"
}}
{{icon "search" class="rag-uploader__search-input__search-icon"}}
<Input
class="rag-uploader__search-input__input"
placeholder={{i18n "discourse_ai.rag.uploads.filter"}}

View File

@ -134,9 +134,7 @@
}
&__uploads-list {
&:has(tr) {
margin-bottom: 20px;
}
margin-bottom: 20px;
tbody {
border-top: none;
@ -165,9 +163,4 @@
&__rag-file-icon {
margin-right: 5px;
}
.hidden-upload-field {
visibility: hidden;
position: absolute;
}
}

View File

@ -1,17 +1,60 @@
.ai-tool-parameter {
margin-bottom: 2em;
padding: 1.5em;
border: 1px solid var(--primary-low);
border-radius: var(--d-input-border-radius);
border-radius: 3px;
background-color: var(--secondary-very-low);
.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;
}
}
.parameter-enum-values {
margin-top: 0.5em;
.enum-value-row {
display: flex;
align-items: center;
margin-bottom: 0.5em;
input[type="text"] {
flex-grow: 1;
margin-right: 0.5em;
}
}
}
}
.ai-tool-editor {
max-width: 80%;
position: relative;
#control-rag_uploads .rag-uploader {
h3,
p {
display: none;
.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;
}
}
}

View File

@ -345,9 +345,7 @@ en:
script: "Script"
parameters: "Parameters"
save: "Save"
parameter_type: "Parameter type"
add_parameter: "Add parameter"
remove_parameter: "Remove"
add_parameter: "Add Parameter"
parameter_required: "Required"
parameter_enum: "Enum"
parameter_name: "Parameter name"
@ -358,7 +356,10 @@ 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"

View File

@ -36,18 +36,21 @@ 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
tool_presets = PageObjects::Components::DMenu.new(find(".ai-tool-list-editor__new-button"))
tool_presets.option(".btn[data-option='exchange_rate'").click
select_kit = PageObjects::Components::SelectKit.new(".ai-tool-editor__presets")
select_kit.expand
select_kit.select_row_by_value("exchange_rate")
required_toggle_css = "#control-parameters-0-required .form-kit__control-checkbox"
enum_toggle_css = "#control-parameters-0-enum .form-kit__control-checkbox"
find(".ai-tool-editor__next").click
expect(page.find(required_toggle_css).checked?).to eq(true)
expect(page.find(enum_toggle_css).checked?).to eq(false)
expect(page.first(".parameter-row__required-toggle").checked?).to eq(true)
expect(page.first(".parameter-row__enum-toggle").checked?).to eq(false)
# not allowed to test yet
expect(page).not_to have_button(".ai-tool-editor__test-button")
@ -62,8 +65,8 @@ describe "AI Tool Management", type: :system do
ensure_can_run_test
expect(page.first(required_toggle_css).checked?).to eq(true)
expect(page.first(enum_toggle_css).checked?).to eq(false)
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"