Follow up to b863ddc94b
Ruby:
* Validate `summary` (the column is `not null`)
* Fix `name` validation (the column has `max_length` 100)
* Fix table annotations
* Accept missing `parameter` attributes (`required, `enum`, `enum_values`)
JS:
* Use native classes
* Don't use ember's array extensions
* Add explicit service injections
* Correct class names
* Use `||=` operator
* Use `store` service to create records
* Remove unused service injections
* Extract consts
* Group actions together
* Use `async`/`await`
* Use `withEventValue`
* Sort html attributes
* Use DButtons `@label` arg
* Use `input` elements instead of Ember's `Input` component (same w/ textarea)
* Remove `btn-default` class (automatically applied by DButton)
* Don't mix `I18n.t` and `i18n` in the same template
* Don't track props that aren't used in a template
* Correct invalid `target.value` code
* Remove unused/invalid `this.parameter`/`onChange` code
* Whitespace
* Use the new service import `inject as service` -> `service`
* Use `Object.entries()`
* Add missing i18n strings
* Fix an error in `addEnumValue` (calling `pushObject` on `undefined`)
* Use `TrackedArray`/`TrackedObject`
* Transform tool `parameters` keys (`enumValues` -> `enum_values`)
This commit is contained in:
parent
a708d4dfa2
commit
a5a39dd2ee
|
@ -1,16 +1,15 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
||||||
async model() {
|
async model() {
|
||||||
const record = this.store.createRecord("ai-tool");
|
return this.store.createRecord("ai-tool");
|
||||||
return record;
|
}
|
||||||
},
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller) {
|
||||||
this._super(controller, model);
|
super.setupController(...arguments);
|
||||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
||||||
|
|
||||||
controller.set("allTools", toolsModel);
|
controller.set("allTools", toolsModel);
|
||||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
controller.set("presets", toolsModel.resultSetMeta.presets);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default class DiscourseAiToolsShowRoute extends DiscourseRoute {
|
||||||
async model(params) {
|
async model(params) {
|
||||||
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
||||||
const id = parseInt(params.id, 10);
|
const id = parseInt(params.id, 10);
|
||||||
return allTools.findBy("id", id);
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
return allTools.find((tool) => tool.id === id);
|
||||||
this._super(controller, model);
|
}
|
||||||
|
|
||||||
|
setupController(controller) {
|
||||||
|
super.setupController(...arguments);
|
||||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
||||||
|
|
||||||
controller.set("allTools", toolsModel);
|
controller.set("allTools", toolsModel);
|
||||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
controller.set("presets", toolsModel.resultSetMeta.presets);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import { service } from "@ember/service";
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default class DiscourseAiToolsRoute extends DiscourseRoute {
|
export default class DiscourseAiToolsRoute extends DiscourseRoute {
|
||||||
|
@service store;
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
return this.store.findAll("ai-tool");
|
return this.store.findAll("ai-tool");
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ module DiscourseAi
|
||||||
:description,
|
:description,
|
||||||
:script,
|
:script,
|
||||||
:summary,
|
:summary,
|
||||||
parameters: %i[name type description],
|
parameters: [:name, :type, :description, :required, :enum, enum_values: []],
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AiTool < ActiveRecord::Base
|
class AiTool < ActiveRecord::Base
|
||||||
validates :name, presence: true, length: { maximum: 255 }
|
validates :name, presence: true, length: { maximum: 100 }
|
||||||
validates :description, presence: true, length: { maximum: 1000 }
|
validates :description, presence: true, length: { maximum: 1000 }
|
||||||
|
validates :summary, presence: true, length: { maximum: 255 }
|
||||||
validates :script, presence: true, length: { maximum: 100_000 }
|
validates :script, presence: true, length: { maximum: 100_000 }
|
||||||
validates :created_by_id, presence: true
|
validates :created_by_id, presence: true
|
||||||
belongs_to :created_by, class_name: "User"
|
belongs_to :created_by, class_name: "User"
|
||||||
|
@ -174,10 +175,12 @@ end
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# description :text not null
|
# description :string not null
|
||||||
|
# summary :string not null
|
||||||
# parameters :jsonb not null
|
# parameters :jsonb not null
|
||||||
# script :text not null
|
# script :text not null
|
||||||
# created_by_id :integer not null
|
# created_by_id :integer not null
|
||||||
|
# enabled :boolean default(TRUE), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import RestAdapter from "discourse/adapters/rest";
|
import RestAdapter from "discourse/adapters/rest";
|
||||||
|
|
||||||
export default class Adapter extends RestAdapter {
|
export default class AiToolAdapter extends RestAdapter {
|
||||||
jsonMode = true;
|
jsonMode = true;
|
||||||
|
|
||||||
basePath() {
|
basePath() {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
|
||||||
const CREATE_ATTRIBUTES = [
|
const CREATE_ATTRIBUTES = [
|
||||||
|
@ -20,8 +21,21 @@ export default class AiTool extends RestModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
workingCopy() {
|
workingCopy() {
|
||||||
let attrs = this.getProperties(CREATE_ATTRIBUTES);
|
const attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||||
attrs.parameters = attrs.parameters || [];
|
|
||||||
return AiTool.create(attrs);
|
attrs.parameters = new TrackedArray(
|
||||||
|
attrs.parameters?.map((p) => {
|
||||||
|
const parameter = new TrackedObject(p);
|
||||||
|
|
||||||
|
if (parameter.enum_values) {
|
||||||
|
parameter.enumValues = new TrackedArray(parameter.enum_values);
|
||||||
|
delete parameter.enum_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameter;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.store.createRecord("ai-tool", attrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
import { tracked } from "@glimmer/tracking";
|
||||||
import { Input } from "@ember/component";
|
import { fn } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
||||||
import { inject as service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import BackButton from "discourse/components/back-button";
|
import BackButton from "discourse/components/back-button";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import Textarea from "discourse/components/d-textarea";
|
|
||||||
import DTooltip from "discourse/components/d-tooltip";
|
import DTooltip from "discourse/components/d-tooltip";
|
||||||
|
import withEventValue from "discourse/helpers/with-event-value";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import AceEditor from "admin/components/ace-editor";
|
import AceEditor from "admin/components/ace-editor";
|
||||||
|
@ -16,9 +17,11 @@ import ComboBox from "select-kit/components/combo-box";
|
||||||
import AiToolParameterEditor from "./ai-tool-parameter-editor";
|
import AiToolParameterEditor from "./ai-tool-parameter-editor";
|
||||||
import AiToolTestModal from "./modal/ai-tool-test-modal";
|
import AiToolTestModal from "./modal/ai-tool-test-modal";
|
||||||
|
|
||||||
|
const ACE_EDITOR_MODE = "javascript";
|
||||||
|
const ACE_EDITOR_THEME = "chrome";
|
||||||
|
|
||||||
export default class AiToolEditor extends Component {
|
export default class AiToolEditor extends Component {
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
|
||||||
@service dialog;
|
@service dialog;
|
||||||
@service modal;
|
@service modal;
|
||||||
@service toasts;
|
@service toasts;
|
||||||
|
@ -26,18 +29,8 @@ export default class AiToolEditor extends Component {
|
||||||
@tracked isSaving = false;
|
@tracked isSaving = false;
|
||||||
@tracked editingModel = null;
|
@tracked editingModel = null;
|
||||||
@tracked showDelete = false;
|
@tracked showDelete = false;
|
||||||
|
|
||||||
@tracked selectedPreset = null;
|
@tracked selectedPreset = null;
|
||||||
|
|
||||||
aceEditorMode = "javascript";
|
|
||||||
aceEditorTheme = "chrome";
|
|
||||||
|
|
||||||
@action
|
|
||||||
updateModel() {
|
|
||||||
this.editingModel = this.args.model.workingCopy();
|
|
||||||
this.showDelete = !this.args.model.isNew;
|
|
||||||
}
|
|
||||||
|
|
||||||
get presets() {
|
get presets() {
|
||||||
return this.args.presets.map((preset) => {
|
return this.args.presets.map((preset) => {
|
||||||
return {
|
return {
|
||||||
|
@ -51,6 +44,12 @@ export default class AiToolEditor extends Component {
|
||||||
return !this.selectedPreset && this.args.model.isNew;
|
return !this.selectedPreset && this.args.model.isNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateModel() {
|
||||||
|
this.editingModel = this.args.model.workingCopy();
|
||||||
|
this.showDelete = !this.args.model.isNew;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
configurePreset() {
|
configurePreset() {
|
||||||
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
|
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
|
||||||
|
@ -64,16 +63,23 @@ export default class AiToolEditor extends Component {
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.args.model.save(
|
const data = this.editingModel.getProperties(
|
||||||
this.editingModel.getProperties(
|
"name",
|
||||||
"name",
|
"description",
|
||||||
"description",
|
"parameters",
|
||||||
"parameters",
|
"script",
|
||||||
"script",
|
"summary"
|
||||||
"summary"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const p of data.parameters) {
|
||||||
|
if (p.enumValues) {
|
||||||
|
p.enum_values = p.enumValues;
|
||||||
|
delete p.enumValues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.args.model.save(data);
|
||||||
|
|
||||||
this.toasts.success({
|
this.toasts.success({
|
||||||
data: { message: I18n.t("discourse_ai.tools.saved") },
|
data: { message: I18n.t("discourse_ai.tools.saved") },
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
|
@ -97,22 +103,15 @@ export default class AiToolEditor extends Component {
|
||||||
delete() {
|
delete() {
|
||||||
return this.dialog.confirm({
|
return this.dialog.confirm({
|
||||||
message: I18n.t("discourse_ai.tools.confirm_delete"),
|
message: I18n.t("discourse_ai.tools.confirm_delete"),
|
||||||
didConfirm: () => {
|
didConfirm: async () => {
|
||||||
return this.args.model.destroyRecord().then(() => {
|
await this.args.model.destroyRecord();
|
||||||
this.args.tools.removeObject(this.args.model);
|
|
||||||
this.router.transitionTo(
|
this.args.tools.removeObject(this.args.model);
|
||||||
"adminPlugins.show.discourse-ai-tools.index"
|
this.router.transitionTo("adminPlugins.show.discourse-ai-tools.index");
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
|
||||||
updateScript(script) {
|
|
||||||
this.editingModel.script = script;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
openTestModal() {
|
openTestModal() {
|
||||||
this.modal.show(AiToolTestModal, {
|
this.modal.show(AiToolTestModal, {
|
||||||
|
@ -129,9 +128,9 @@ export default class AiToolEditor extends Component {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="form-horizontal ai-tool-editor"
|
|
||||||
{{didUpdate this.updateModel @model.id}}
|
|
||||||
{{didInsert this.updateModel @model.id}}
|
{{didInsert this.updateModel @model.id}}
|
||||||
|
{{didUpdate this.updateModel @model.id}}
|
||||||
|
class="form-horizontal ai-tool-editor"
|
||||||
>
|
>
|
||||||
{{#if this.showPresets}}
|
{{#if this.showPresets}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
@ -145,18 +144,18 @@ export default class AiToolEditor extends Component {
|
||||||
|
|
||||||
<div class="control-group ai-llm-editor__action_panel">
|
<div class="control-group ai-llm-editor__action_panel">
|
||||||
<DButton
|
<DButton
|
||||||
class="ai-tool-editor__next"
|
|
||||||
@action={{this.configurePreset}}
|
@action={{this.configurePreset}}
|
||||||
>
|
@label="discourse_ai.tools.next.title"
|
||||||
{{I18n.t "discourse_ai.tools.next.title"}}
|
class="ai-tool-editor__next"
|
||||||
</DButton>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{I18n.t "discourse_ai.tools.name"}}</label>
|
<label>{{I18n.t "discourse_ai.tools.name"}}</label>
|
||||||
<Input
|
<input
|
||||||
@type="text"
|
{{on "input" (withEventValue (fn (mut this.editingModel.name)))}}
|
||||||
@value={{this.editingModel.name}}
|
value={{this.editingModel.name}}
|
||||||
|
type="text"
|
||||||
class="ai-tool-editor__name"
|
class="ai-tool-editor__name"
|
||||||
/>
|
/>
|
||||||
<DTooltip
|
<DTooltip
|
||||||
|
@ -164,19 +163,25 @@ export default class AiToolEditor extends Component {
|
||||||
@content={{I18n.t "discourse_ai.tools.name_help"}}
|
@content={{I18n.t "discourse_ai.tools.name_help"}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{I18n.t "discourse_ai.tools.description"}}</label>
|
<label>{{I18n.t "discourse_ai.tools.description"}}</label>
|
||||||
<Textarea
|
<textarea
|
||||||
@value={{this.editingModel.description}}
|
{{on
|
||||||
class="ai-tool-editor__description input-xxlarge"
|
"input"
|
||||||
|
(withEventValue (fn (mut this.editingModel.description)))
|
||||||
|
}}
|
||||||
placeholder={{I18n.t "discourse_ai.tools.description_help"}}
|
placeholder={{I18n.t "discourse_ai.tools.description_help"}}
|
||||||
/>
|
class="ai-tool-editor__description input-xxlarge"
|
||||||
|
>{{this.editingModel.description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{I18n.t "discourse_ai.tools.summary"}}</label>
|
<label>{{I18n.t "discourse_ai.tools.summary"}}</label>
|
||||||
<Input
|
<input
|
||||||
@type="text"
|
{{on "input" (withEventValue (fn (mut this.editingModel.summary)))}}
|
||||||
@value={{this.editingModel.summary}}
|
value={{this.editingModel.summary}}
|
||||||
|
type="text"
|
||||||
class="ai-tool-editor__summary input-xxlarge"
|
class="ai-tool-editor__summary input-xxlarge"
|
||||||
/>
|
/>
|
||||||
<DTooltip
|
<DTooltip
|
||||||
|
@ -184,37 +189,43 @@ export default class AiToolEditor extends Component {
|
||||||
@content={{I18n.t "discourse_ai.tools.summary_help"}}
|
@content={{I18n.t "discourse_ai.tools.summary_help"}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{I18n.t "discourse_ai.tools.parameters"}}</label>
|
<label>{{I18n.t "discourse_ai.tools.parameters"}}</label>
|
||||||
<AiToolParameterEditor @parameters={{this.editingModel.parameters}} />
|
<AiToolParameterEditor @parameters={{this.editingModel.parameters}} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{I18n.t "discourse_ai.tools.script"}}</label>
|
<label>{{I18n.t "discourse_ai.tools.script"}}</label>
|
||||||
<AceEditor
|
<AceEditor
|
||||||
@content={{this.editingModel.script}}
|
@content={{this.editingModel.script}}
|
||||||
@mode={{this.aceEditorMode}}
|
@onChange={{withEventValue (fn (mut this.editingModel.script))}}
|
||||||
@theme={{this.aceEditorTheme}}
|
@mode={{ACE_EDITOR_MODE}}
|
||||||
@onChange={{this.updateScript}}
|
@theme={{ACE_EDITOR_THEME}}
|
||||||
@editorId="ai-tool-script-editor"
|
@editorId="ai-tool-script-editor"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group ai-tool-editor__action_panel">
|
<div class="control-group ai-tool-editor__action_panel">
|
||||||
<DButton
|
<DButton
|
||||||
@action={{this.openTestModal}}
|
@action={{this.openTestModal}}
|
||||||
class="btn-default ai-tool-editor__test-button"
|
@label="discourse_ai.tools.test"
|
||||||
>{{I18n.t "discourse_ai.tools.test"}}</DButton>
|
class="ai-tool-editor__test-button"
|
||||||
|
/>
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
class="btn-primary ai-tool-editor__save"
|
|
||||||
@action={{this.save}}
|
@action={{this.save}}
|
||||||
|
@label="discourse_ai.tools.save"
|
||||||
@disabled={{this.isSaving}}
|
@disabled={{this.isSaving}}
|
||||||
>{{I18n.t "discourse_ai.tools.save"}}</DButton>
|
class="btn-primary ai-tool-editor__save"
|
||||||
|
/>
|
||||||
|
|
||||||
{{#if this.showDelete}}
|
{{#if this.showDelete}}
|
||||||
<DButton
|
<DButton
|
||||||
@action={{this.delete}}
|
@action={{this.delete}}
|
||||||
|
@label="discourse_ai.tools.delete"
|
||||||
class="btn-danger ai-tool-editor__delete"
|
class="btn-danger ai-tool-editor__delete"
|
||||||
>
|
/>
|
||||||
{{I18n.t "discourse_ai.tools.delete"}}
|
|
||||||
</DButton>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { LinkTo } from "@ember/routing";
|
import { LinkTo } from "@ember/routing";
|
||||||
import icon from "discourse-common/helpers/d-icon";
|
import icon from "discourse-common/helpers/d-icon";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="ai-tool-list-editor__current admin-detail pull-left">
|
<section class="ai-tool-list-editor__current admin-detail pull-left">
|
||||||
<div class="ai-tool-list-editor__header">
|
<div class="ai-tool-list-editor__header">
|
||||||
<h3>{{i18n "discourse_ai.tools.short_title"}}</h3>
|
<h3>{{I18n.t "discourse_ai.tools.short_title"}}</h3>
|
||||||
<LinkTo
|
<LinkTo
|
||||||
@route="adminPlugins.show.discourse-ai-tools.new"
|
@route="adminPlugins.show.discourse-ai-tools.new"
|
||||||
class="btn btn-small btn-primary ai-tool-list-editor__new-button"
|
class="btn btn-small btn-primary ai-tool-list-editor__new-button"
|
||||||
|
@ -37,7 +36,7 @@ import I18n from "discourse-i18n";
|
||||||
@route="adminPlugins.show.discourse-ai-tools.show"
|
@route="adminPlugins.show.discourse-ai-tools.show"
|
||||||
@model={{tool}}
|
@model={{tool}}
|
||||||
class="btn btn-text btn-small"
|
class="btn btn-text btn-small"
|
||||||
>{{i18n "discourse_ai.tools.edit"}} </LinkTo>
|
>{{I18n.t "discourse_ai.tools.edit"}}</LinkTo>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { tracked } from "@glimmer/tracking";
|
|
||||||
import { Input } from "@ember/component";
|
|
||||||
import { fn } from "@ember/helper";
|
import { fn } from "@ember/helper";
|
||||||
import { on } from "@ember/modifier";
|
import { on } from "@ember/modifier";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
|
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
|
import withEventValue from "discourse/helpers/with-event-value";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import ComboBox from "select-kit/components/combo-box";
|
import ComboBox from "select-kit/components/combo-box";
|
||||||
|
|
||||||
|
@ -16,145 +16,135 @@ const PARAMETER_TYPES = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class AiToolParameterEditor extends Component {
|
export default class AiToolParameterEditor extends Component {
|
||||||
@tracked parameters = [];
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addParameter() {
|
addParameter() {
|
||||||
this.args.parameters.pushObject({
|
this.args.parameters.push(
|
||||||
name: "",
|
new TrackedObject({
|
||||||
description: "",
|
name: "",
|
||||||
type: "string",
|
description: "",
|
||||||
required: false,
|
type: "string",
|
||||||
enum: false,
|
required: false,
|
||||||
enumValues: [],
|
enum: false,
|
||||||
});
|
enumValues: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeParameter(parameter) {
|
removeParameter(parameter) {
|
||||||
this.args.parameters.removeObject(parameter);
|
const index = this.args.parameters.indexOf(parameter);
|
||||||
|
this.args.parameters.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateParameter(parameter, field, value) {
|
toggleRequired(parameter, event) {
|
||||||
parameter[field] = value;
|
parameter.required = event.target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
toggleEnum(parameter) {
|
toggleEnum(parameter) {
|
||||||
parameter.enum = !parameter.enum;
|
parameter.enum = !parameter.enum;
|
||||||
if (!parameter.enum) {
|
if (!parameter.enum) {
|
||||||
parameter.enumValues = [];
|
parameter.enumValues = null;
|
||||||
}
|
}
|
||||||
this.args.onChange(this.parameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addEnumValue(parameter) {
|
addEnumValue(parameter) {
|
||||||
parameter.enumValues.pushObject("");
|
parameter.enumValues ||= new TrackedArray();
|
||||||
|
parameter.enumValues.push("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
removeEnumValue(parameter, index) {
|
removeEnumValue(parameter, index) {
|
||||||
parameter.enumValues.removeAt(index);
|
parameter.enumValues.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateEnumValue(parameter, index, event) {
|
||||||
|
parameter.enumValues[index] = event.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
{{#each @parameters as |parameter|}}
|
{{#each @parameters as |parameter|}}
|
||||||
<div class="ai-tool-parameter">
|
<div class="ai-tool-parameter">
|
||||||
<div class="parameter-row">
|
<div class="parameter-row">
|
||||||
<Input
|
<input
|
||||||
@type="text"
|
{{on "input" (withEventValue (fn (mut parameter.name)))}}
|
||||||
@value={{parameter.name}}
|
value={{parameter.name}}
|
||||||
|
type="text"
|
||||||
placeholder={{I18n.t "discourse_ai.tools.parameter_name"}}
|
placeholder={{I18n.t "discourse_ai.tools.parameter_name"}}
|
||||||
/>
|
/>
|
||||||
<ComboBox @value={{parameter.type}} @content={{PARAMETER_TYPES}} />
|
<ComboBox @value={{parameter.type}} @content={{PARAMETER_TYPES}} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="parameter-row">
|
<div class="parameter-row">
|
||||||
<Input
|
<input
|
||||||
@type="text"
|
{{on "input" (withEventValue (fn (mut parameter.description)))}}
|
||||||
@value={{parameter.description}}
|
value={{parameter.description}}
|
||||||
|
type="text"
|
||||||
placeholder={{I18n.t "discourse_ai.tools.parameter_description"}}
|
placeholder={{I18n.t "discourse_ai.tools.parameter_description"}}
|
||||||
{{on
|
|
||||||
"input"
|
|
||||||
(fn
|
|
||||||
this.updateParameter
|
|
||||||
parameter
|
|
||||||
"description"
|
|
||||||
value="target.value"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="parameter-row">
|
<div class="parameter-row">
|
||||||
<label>
|
<label>
|
||||||
<Input
|
<input
|
||||||
@type="checkbox"
|
{{on "input" (fn this.toggleRequired parameter)}}
|
||||||
@checked={{parameter.required}}
|
checked={{parameter.required}}
|
||||||
{{on
|
type="checkbox"
|
||||||
"change"
|
|
||||||
(fn
|
|
||||||
this.updateParameter
|
|
||||||
parameter
|
|
||||||
"required"
|
|
||||||
value="target.checked"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{{I18n.t "discourse_ai.tools.parameter_required"}}
|
{{I18n.t "discourse_ai.tools.parameter_required"}}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<Input
|
<input
|
||||||
@type="checkbox"
|
{{on "input" (fn this.toggleEnum parameter)}}
|
||||||
@checked={{parameter.enum}}
|
checked={{parameter.enum}}
|
||||||
{{on "change" (fn this.toggleEnum parameter)}}
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@icon="trash-alt"
|
|
||||||
@action={{fn this.removeParameter parameter}}
|
@action={{fn this.removeParameter parameter}}
|
||||||
|
@icon="trash-alt"
|
||||||
class="btn-danger"
|
class="btn-danger"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if parameter.enum}}
|
{{#if parameter.enum}}
|
||||||
<div class="parameter-enum-values">
|
<div class="parameter-enum-values">
|
||||||
{{#each parameter.enumValues as |enumValue enumIndex|}}
|
{{#each parameter.enumValues as |enumValue enumIndex|}}
|
||||||
<div class="enum-value-row">
|
<div class="enum-value-row">
|
||||||
<Input
|
<input
|
||||||
@type="text"
|
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}
|
||||||
@value={{enumValue}}
|
value={{enumValue}}
|
||||||
|
type="text"
|
||||||
placeholder={{I18n.t "discourse_ai.tools.enum_value"}}
|
placeholder={{I18n.t "discourse_ai.tools.enum_value"}}
|
||||||
{{on
|
|
||||||
"input"
|
|
||||||
(fn
|
|
||||||
this.updateParameter
|
|
||||||
parameter.enumValues
|
|
||||||
enumIndex
|
|
||||||
value="target.value"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<DButton
|
<DButton
|
||||||
@icon="trash-alt"
|
|
||||||
@action={{fn this.removeEnumValue parameter enumIndex}}
|
@action={{fn this.removeEnumValue parameter enumIndex}}
|
||||||
|
@icon="trash-alt"
|
||||||
class="btn-danger"
|
class="btn-danger"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@icon="plus"
|
|
||||||
@action={{fn this.addEnumValue parameter}}
|
@action={{fn this.addEnumValue parameter}}
|
||||||
@label="discourse_ai.tools.add_enum_value"
|
@label="discourse_ai.tools.add_enum_value"
|
||||||
|
@icon="plus"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<DButton
|
<DButton
|
||||||
@icon="plus"
|
|
||||||
@action={{this.addParameter}}
|
@action={{this.addParameter}}
|
||||||
@label="discourse_ai.tools.add_parameter"
|
@label="discourse_ai.tools.add_parameter"
|
||||||
|
@icon="plus"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import I18n from "discourse-i18n";
|
||||||
import { jsonToHtml } from "../../lib/utilities";
|
import { jsonToHtml } from "../../lib/utilities";
|
||||||
|
|
||||||
export default class AiToolTestModal extends Component {
|
export default class AiToolTestModal extends Component {
|
||||||
@tracked testResult = null;
|
@tracked testResult;
|
||||||
@tracked isLoading = false;
|
@tracked isLoading = false;
|
||||||
@tracked parameterValues = {};
|
parameterValues = {};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateParameter(name, event) {
|
updateParameter(name, event) {
|
||||||
|
@ -24,16 +24,14 @@ export default class AiToolTestModal extends Component {
|
||||||
async runTest() {
|
async runTest() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
const data = JSON.stringify({
|
|
||||||
ai_tool: this.args.model.tool,
|
|
||||||
parameters: this.parameterValues,
|
|
||||||
}); // required given this is a POST
|
|
||||||
|
|
||||||
const response = await ajax(
|
const response = await ajax(
|
||||||
"/admin/plugins/discourse-ai/ai-tools/test.json",
|
"/admin/plugins/discourse-ai/ai-tools/test.json",
|
||||||
{
|
{
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data,
|
data: JSON.stringify({
|
||||||
|
ai_tool: this.args.model.tool,
|
||||||
|
parameters: this.parameterValues,
|
||||||
|
}),
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -49,20 +47,21 @@ export default class AiToolTestModal extends Component {
|
||||||
<DModal
|
<DModal
|
||||||
@title={{I18n.t "discourse_ai.tools.test_modal.title"}}
|
@title={{I18n.t "discourse_ai.tools.test_modal.title"}}
|
||||||
@closeModal={{@closeModal}}
|
@closeModal={{@closeModal}}
|
||||||
class="ai-tool-test-modal"
|
|
||||||
@bodyClass="ai-tool-test-modal__body"
|
@bodyClass="ai-tool-test-modal__body"
|
||||||
|
class="ai-tool-test-modal"
|
||||||
>
|
>
|
||||||
<:body>
|
<:body>
|
||||||
{{#each @model.tool.parameters as |param|}}
|
{{#each @model.tool.parameters as |param|}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label>{{param.name}}</label>
|
<label>{{param.name}}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
|
||||||
name={{param.name}}
|
|
||||||
{{on "input" (fn this.updateParameter param.name)}}
|
{{on "input" (fn this.updateParameter param.name)}}
|
||||||
|
name={{param.name}}
|
||||||
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{#if this.testResult}}
|
{{#if this.testResult}}
|
||||||
<div class="ai-tool-test-modal__test-result">
|
<div class="ai-tool-test-modal__test-result">
|
||||||
<h3>{{I18n.t "discourse_ai.tools.test_modal.result"}}</h3>
|
<h3>{{I18n.t "discourse_ai.tools.test_modal.result"}}</h3>
|
||||||
|
@ -70,6 +69,7 @@ export default class AiToolTestModal extends Component {
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</:body>
|
</:body>
|
||||||
|
|
||||||
<:footer>
|
<:footer>
|
||||||
<DButton
|
<DButton
|
||||||
@action={{this.runTest}}
|
@action={{this.runTest}}
|
||||||
|
|
|
@ -8,30 +8,27 @@ export function jsonToHtml(json) {
|
||||||
if (typeof json !== "object") {
|
if (typeof json !== "object") {
|
||||||
return escapeExpression(json);
|
return escapeExpression(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = "<ul>";
|
let html = "<ul>";
|
||||||
for (let key in json) {
|
|
||||||
if (!json.hasOwnProperty(key)) {
|
for (let [key, value] of Object.entries(json)) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
html += "<li>";
|
html += "<li>";
|
||||||
if (typeof json[key] === "object" && Array.isArray(json[key])) {
|
key = escapeExpression(key);
|
||||||
html += `<strong>${escapeExpression(key)}:</strong> ${jsonToHtml(
|
|
||||||
json[key]
|
if (typeof value === "object" && Array.isArray(value)) {
|
||||||
)}`;
|
html += `<strong>${key}:</strong> ${jsonToHtml(value)}`;
|
||||||
} else if (typeof json[key] === "object") {
|
} else if (typeof value === "object") {
|
||||||
html += `<strong>${escapeExpression(key)}:</strong> <ul><li>${jsonToHtml(
|
html += `<strong>${key}:</strong> <ul><li>${jsonToHtml(value)}</li></ul>`;
|
||||||
json[key]
|
|
||||||
)}</li></ul>`;
|
|
||||||
} else {
|
} else {
|
||||||
let value = json[key];
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
value = escapeExpression(value);
|
value = escapeExpression(value).replace(/\n/g, "<br>");
|
||||||
value = value.replace(/\n/g, "<br>");
|
|
||||||
}
|
}
|
||||||
html += `<strong>${escapeExpression(key)}:</strong> ${value}`;
|
html += `<strong>${key}:</strong> ${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</li>";
|
html += "</li>";
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</ul>";
|
html += "</ul>";
|
||||||
return htmlSafe(html);
|
return htmlSafe(html);
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,8 @@ en:
|
||||||
parameter_enum: "Enum"
|
parameter_enum: "Enum"
|
||||||
parameter_name: "Parameter Name"
|
parameter_name: "Parameter Name"
|
||||||
parameter_description: "Parameter Description"
|
parameter_description: "Parameter Description"
|
||||||
|
enum_value: "Enum value"
|
||||||
|
add_enum_value: "Add enum value"
|
||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
test: "Run Test"
|
test: "Run Test"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
|
|
Loading…
Reference in New Issue