Skip to content

Commit a5a39dd

Browse files
authored
DEV: Clean up after #677 (#694)
Follow up to b863ddc 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`)
1 parent a708d4d commit a5a39dd

File tree

13 files changed

+197
-178
lines changed

13 files changed

+197
-178
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import DiscourseRoute from "discourse/routes/discourse";
22

3-
export default DiscourseRoute.extend({
3+
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
44
async model() {
5-
const record = this.store.createRecord("ai-tool");
6-
return record;
7-
},
5+
return this.store.createRecord("ai-tool");
6+
}
87

9-
setupController(controller, model) {
10-
this._super(controller, model);
8+
setupController(controller) {
9+
super.setupController(...arguments);
1110
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
1211

1312
controller.set("allTools", toolsModel);
1413
controller.set("presets", toolsModel.resultSetMeta.presets);
15-
},
16-
});
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import DiscourseRoute from "discourse/routes/discourse";
22

3-
export default DiscourseRoute.extend({
3+
export default class DiscourseAiToolsShowRoute extends DiscourseRoute {
44
async model(params) {
55
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
66
const id = parseInt(params.id, 10);
7-
return allTools.findBy("id", id);
8-
},
97

10-
setupController(controller, model) {
11-
this._super(controller, model);
8+
return allTools.find((tool) => tool.id === id);
9+
}
10+
11+
setupController(controller) {
12+
super.setupController(...arguments);
1213
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
1314

1415
controller.set("allTools", toolsModel);
1516
controller.set("presets", toolsModel.resultSetMeta.presets);
16-
},
17-
});
17+
}
18+
}

admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-tools.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { service } from "@ember/service";
12
import DiscourseRoute from "discourse/routes/discourse";
23

34
export default class DiscourseAiToolsRoute extends DiscourseRoute {
5+
@service store;
6+
47
model() {
58
return this.store.findAll("ai-tool");
69
}

app/controllers/discourse_ai/admin/ai_tools_controller.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def ai_tool_params
8181
:description,
8282
:script,
8383
:summary,
84-
parameters: %i[name type description],
84+
parameters: [:name, :type, :description, :required, :enum, enum_values: []],
8585
)
8686
end
8787
end

app/models/ai_tool.rb

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# frozen_string_literal: true
22

33
class AiTool < ActiveRecord::Base
4-
validates :name, presence: true, length: { maximum: 255 }
4+
validates :name, presence: true, length: { maximum: 100 }
55
validates :description, presence: true, length: { maximum: 1000 }
6+
validates :summary, presence: true, length: { maximum: 255 }
67
validates :script, presence: true, length: { maximum: 100_000 }
78
validates :created_by_id, presence: true
89
belongs_to :created_by, class_name: "User"
@@ -174,10 +175,12 @@ def self.presets
174175
#
175176
# id :bigint not null, primary key
176177
# name :string not null
177-
# description :text not null
178+
# description :string not null
179+
# summary :string not null
178180
# parameters :jsonb not null
179181
# script :text not null
180182
# created_by_id :integer not null
183+
# enabled :boolean default(TRUE), not null
181184
# created_at :datetime not null
182185
# updated_at :datetime not null
183186
#

assets/javascripts/discourse/admin/adapters/ai-tool.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import RestAdapter from "discourse/adapters/rest";
22

3-
export default class Adapter extends RestAdapter {
3+
export default class AiToolAdapter extends RestAdapter {
44
jsonMode = true;
55

66
basePath() {

assets/javascripts/discourse/admin/models/ai-tool.js

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
12
import RestModel from "discourse/models/rest";
23

34
const CREATE_ATTRIBUTES = [
@@ -20,8 +21,21 @@ export default class AiTool extends RestModel {
2021
}
2122

2223
workingCopy() {
23-
let attrs = this.getProperties(CREATE_ATTRIBUTES);
24-
attrs.parameters = attrs.parameters || [];
25-
return AiTool.create(attrs);
24+
const attrs = this.getProperties(CREATE_ATTRIBUTES);
25+
26+
attrs.parameters = new TrackedArray(
27+
attrs.parameters?.map((p) => {
28+
const parameter = new TrackedObject(p);
29+
30+
if (parameter.enum_values) {
31+
parameter.enumValues = new TrackedArray(parameter.enum_values);
32+
delete parameter.enum_values;
33+
}
34+
35+
return parameter;
36+
})
37+
);
38+
39+
return this.store.createRecord("ai-tool", attrs);
2640
}
2741
}

assets/javascripts/discourse/components/ai-tool-editor.gjs

+71-60
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,36 @@
11
import Component from "@glimmer/component";
22
import { tracked } from "@glimmer/tracking";
3-
import { Input } from "@ember/component";
3+
import { fn } from "@ember/helper";
4+
import { on } from "@ember/modifier";
45
import { action } from "@ember/object";
56
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
67
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
7-
import { inject as service } from "@ember/service";
8+
import { service } from "@ember/service";
89
import BackButton from "discourse/components/back-button";
910
import DButton from "discourse/components/d-button";
10-
import Textarea from "discourse/components/d-textarea";
1111
import DTooltip from "discourse/components/d-tooltip";
12+
import withEventValue from "discourse/helpers/with-event-value";
1213
import { popupAjaxError } from "discourse/lib/ajax-error";
1314
import I18n from "discourse-i18n";
1415
import AceEditor from "admin/components/ace-editor";
1516
import ComboBox from "select-kit/components/combo-box";
1617
import AiToolParameterEditor from "./ai-tool-parameter-editor";
1718
import AiToolTestModal from "./modal/ai-tool-test-modal";
1819

20+
const ACE_EDITOR_MODE = "javascript";
21+
const ACE_EDITOR_THEME = "chrome";
22+
1923
export default class AiToolEditor extends Component {
2024
@service router;
21-
@service store;
2225
@service dialog;
2326
@service modal;
2427
@service toasts;
2528

2629
@tracked isSaving = false;
2730
@tracked editingModel = null;
2831
@tracked showDelete = false;
29-
3032
@tracked selectedPreset = null;
3133

32-
aceEditorMode = "javascript";
33-
aceEditorTheme = "chrome";
34-
35-
@action
36-
updateModel() {
37-
this.editingModel = this.args.model.workingCopy();
38-
this.showDelete = !this.args.model.isNew;
39-
}
40-
4134
get presets() {
4235
return this.args.presets.map((preset) => {
4336
return {
@@ -51,6 +44,12 @@ export default class AiToolEditor extends Component {
5144
return !this.selectedPreset && this.args.model.isNew;
5245
}
5346

47+
@action
48+
updateModel() {
49+
this.editingModel = this.args.model.workingCopy();
50+
this.showDelete = !this.args.model.isNew;
51+
}
52+
5453
@action
5554
configurePreset() {
5655
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
@@ -64,16 +63,23 @@ export default class AiToolEditor extends Component {
6463
this.isSaving = true;
6564

6665
try {
67-
await this.args.model.save(
68-
this.editingModel.getProperties(
69-
"name",
70-
"description",
71-
"parameters",
72-
"script",
73-
"summary"
74-
)
66+
const data = this.editingModel.getProperties(
67+
"name",
68+
"description",
69+
"parameters",
70+
"script",
71+
"summary"
7572
);
7673

74+
for (const p of data.parameters) {
75+
if (p.enumValues) {
76+
p.enum_values = p.enumValues;
77+
delete p.enumValues;
78+
}
79+
}
80+
81+
await this.args.model.save(data);
82+
7783
this.toasts.success({
7884
data: { message: I18n.t("discourse_ai.tools.saved") },
7985
duration: 2000,
@@ -97,22 +103,15 @@ export default class AiToolEditor extends Component {
97103
delete() {
98104
return this.dialog.confirm({
99105
message: I18n.t("discourse_ai.tools.confirm_delete"),
100-
didConfirm: () => {
101-
return this.args.model.destroyRecord().then(() => {
102-
this.args.tools.removeObject(this.args.model);
103-
this.router.transitionTo(
104-
"adminPlugins.show.discourse-ai-tools.index"
105-
);
106-
});
106+
didConfirm: async () => {
107+
await this.args.model.destroyRecord();
108+
109+
this.args.tools.removeObject(this.args.model);
110+
this.router.transitionTo("adminPlugins.show.discourse-ai-tools.index");
107111
},
108112
});
109113
}
110114

111-
@action
112-
updateScript(script) {
113-
this.editingModel.script = script;
114-
}
115-
116115
@action
117116
openTestModal() {
118117
this.modal.show(AiToolTestModal, {
@@ -129,9 +128,9 @@ export default class AiToolEditor extends Component {
129128
/>
130129

131130
<form
132-
class="form-horizontal ai-tool-editor"
133-
{{didUpdate this.updateModel @model.id}}
134131
{{didInsert this.updateModel @model.id}}
132+
{{didUpdate this.updateModel @model.id}}
133+
class="form-horizontal ai-tool-editor"
135134
>
136135
{{#if this.showPresets}}
137136
<div class="control-group">
@@ -145,76 +144,88 @@ export default class AiToolEditor extends Component {
145144

146145
<div class="control-group ai-llm-editor__action_panel">
147146
<DButton
148-
class="ai-tool-editor__next"
149147
@action={{this.configurePreset}}
150-
>
151-
{{I18n.t "discourse_ai.tools.next.title"}}
152-
</DButton>
148+
@label="discourse_ai.tools.next.title"
149+
class="ai-tool-editor__next"
150+
/>
153151
</div>
154152
{{else}}
155153
<div class="control-group">
156154
<label>{{I18n.t "discourse_ai.tools.name"}}</label>
157-
<Input
158-
@type="text"
159-
@value={{this.editingModel.name}}
155+
<input
156+
{{on "input" (withEventValue (fn (mut this.editingModel.name)))}}
157+
value={{this.editingModel.name}}
158+
type="text"
160159
class="ai-tool-editor__name"
161160
/>
162161
<DTooltip
163162
@icon="question-circle"
164163
@content={{I18n.t "discourse_ai.tools.name_help"}}
165164
/>
166165
</div>
166+
167167
<div class="control-group">
168168
<label>{{I18n.t "discourse_ai.tools.description"}}</label>
169-
<Textarea
170-
@value={{this.editingModel.description}}
171-
class="ai-tool-editor__description input-xxlarge"
169+
<textarea
170+
{{on
171+
"input"
172+
(withEventValue (fn (mut this.editingModel.description)))
173+
}}
172174
placeholder={{I18n.t "discourse_ai.tools.description_help"}}
173-
/>
175+
class="ai-tool-editor__description input-xxlarge"
176+
>{{this.editingModel.description}}</textarea>
174177
</div>
178+
175179
<div class="control-group">
176180
<label>{{I18n.t "discourse_ai.tools.summary"}}</label>
177-
<Input
178-
@type="text"
179-
@value={{this.editingModel.summary}}
181+
<input
182+
{{on "input" (withEventValue (fn (mut this.editingModel.summary)))}}
183+
value={{this.editingModel.summary}}
184+
type="text"
180185
class="ai-tool-editor__summary input-xxlarge"
181186
/>
182187
<DTooltip
183188
@icon="question-circle"
184189
@content={{I18n.t "discourse_ai.tools.summary_help"}}
185190
/>
186191
</div>
192+
187193
<div class="control-group">
188194
<label>{{I18n.t "discourse_ai.tools.parameters"}}</label>
189195
<AiToolParameterEditor @parameters={{this.editingModel.parameters}} />
190196
</div>
197+
191198
<div class="control-group">
192199
<label>{{I18n.t "discourse_ai.tools.script"}}</label>
193200
<AceEditor
194201
@content={{this.editingModel.script}}
195-
@mode={{this.aceEditorMode}}
196-
@theme={{this.aceEditorTheme}}
197-
@onChange={{this.updateScript}}
202+
@onChange={{withEventValue (fn (mut this.editingModel.script))}}
203+
@mode={{ACE_EDITOR_MODE}}
204+
@theme={{ACE_EDITOR_THEME}}
198205
@editorId="ai-tool-script-editor"
199206
/>
200207
</div>
208+
201209
<div class="control-group ai-tool-editor__action_panel">
202210
<DButton
203211
@action={{this.openTestModal}}
204-
class="btn-default ai-tool-editor__test-button"
205-
>{{I18n.t "discourse_ai.tools.test"}}</DButton>
212+
@label="discourse_ai.tools.test"
213+
class="ai-tool-editor__test-button"
214+
/>
215+
206216
<DButton
207-
class="btn-primary ai-tool-editor__save"
208217
@action={{this.save}}
218+
@label="discourse_ai.tools.save"
209219
@disabled={{this.isSaving}}
210-
>{{I18n.t "discourse_ai.tools.save"}}</DButton>
220+
class="btn-primary ai-tool-editor__save"
221+
/>
222+
211223
{{#if this.showDelete}}
212224
<DButton
213225
@action={{this.delete}}
226+
@label="discourse_ai.tools.delete"
214227
class="btn-danger ai-tool-editor__delete"
215-
>
216-
{{I18n.t "discourse_ai.tools.delete"}}
217-
</DButton>
228+
/>
218229
{{/if}}
219230
</div>
220231
{{/if}}

0 commit comments

Comments
 (0)