Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 107f144

Browse files
authored
DEV: Convert tool editor to form kit (#1135)
* DEV: Make tool presets a dropdown * DEV: Select tool presets via DMenu instead * WIP * WIP: Add parameter types, uploader, script, etc. * WIP * updates * fix lint * FIX: spec * fixes
1 parent 269f169 commit 107f144

File tree

11 files changed

+418
-491
lines changed

11 files changed

+418
-491
lines changed
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import DiscourseRoute from "discourse/routes/discourse";
22

33
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
4+
beforeModel(transition) {
5+
this.preset = transition.to.queryParams.presetId || "empty_tool";
6+
}
7+
48
async model() {
59
return this.store.createRecord("ai-tool");
610
}
711

812
setupController(controller) {
913
super.setupController(...arguments);
1014
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
11-
1215
controller.set("allTools", toolsModel);
1316
controller.set("presets", toolsModel.resultSetMeta.presets);
1417
controller.set("llms", toolsModel.resultSetMeta.llms);
1518
controller.set("settings", toolsModel.resultSetMeta.settings);
19+
controller.set("selectedPreset", this.preset);
1620
}
1721
}

admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-tools/new.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
@presets={{this.presets}}
66
@llms={{this.llms}}
77
@settings={{this.settings}}
8+
@selectedPreset={{this.selectedPreset}}
89
/>
910
</section>
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
import Component from "@glimmer/component";
2+
import { tracked } from "@glimmer/tracking";
3+
import { fn, hash } from "@ember/helper";
4+
import { action } from "@ember/object";
5+
import { service } from "@ember/service";
6+
import Form from "discourse/components/form";
7+
import { popupAjaxError } from "discourse/lib/ajax-error";
8+
import { i18n } from "discourse-i18n";
9+
import AiToolTestModal from "./modal/ai-tool-test-modal";
10+
import RagOptions from "./rag-options";
11+
import RagUploader from "./rag-uploader";
12+
13+
export default class AiToolEditorForm extends Component {
14+
@service modal;
15+
@service siteSettings;
16+
@service dialog;
17+
@service router;
18+
@service toasts;
19+
20+
@tracked uploadedFiles = [];
21+
@tracked isSaving = false;
22+
23+
PARAMETER_TYPES = [
24+
{ name: "string", id: "string" },
25+
{ name: "number", id: "number" },
26+
{ name: "boolean", id: "boolean" },
27+
{ name: "array", id: "array" },
28+
];
29+
30+
get formData() {
31+
return {
32+
name: this.args.editingModel.name || "",
33+
tool_name: this.args.editingModel.tool_name || "",
34+
description: this.args.editingModel.description || "",
35+
summary: this.args.editingModel.summary || "",
36+
parameters: this.args.editingModel.parameters || [],
37+
script: this.args.editingModel.script || "",
38+
rag_uploads: this.args.editingModel.rag_uploads || [],
39+
};
40+
}
41+
42+
@action
43+
async save(data) {
44+
this.isSaving = true;
45+
46+
try {
47+
await this.args.model.save(data);
48+
49+
this.toasts.success({
50+
data: { message: i18n("discourse_ai.tools.saved") },
51+
duration: 2000,
52+
});
53+
54+
if (!this.args.tools.any((tool) => tool.id === this.args.model.id)) {
55+
this.args.tools.pushObject(this.args.model);
56+
}
57+
58+
this.router.transitionTo(
59+
"adminPlugins.show.discourse-ai-tools.edit",
60+
this.args.model
61+
);
62+
} catch (e) {
63+
popupAjaxError(e);
64+
} finally {
65+
this.isSaving = false;
66+
}
67+
}
68+
69+
@action
70+
delete() {
71+
return this.dialog.confirm({
72+
message: i18n("discourse_ai.tools.confirm_delete"),
73+
74+
didConfirm: async () => {
75+
await this.args.model.destroyRecord();
76+
this.args.tools.removeObject(this.args.model);
77+
this.router.transitionTo("adminPlugins.show.discourse-ai-tools.index");
78+
},
79+
});
80+
}
81+
82+
@action
83+
updateUploads(addItemToCollection, uploads) {
84+
const uniqueUploads = uploads.filter(
85+
(upload) => !this.uploadedFiles.some((file) => file.id === upload.id)
86+
);
87+
addItemToCollection("rag_uploads", uniqueUploads);
88+
this.uploadedFiles = [...this.uploadedFiles, ...uniqueUploads];
89+
}
90+
91+
@action
92+
removeUpload(form, upload) {
93+
this.uploadedFiles = this.uploadedFiles.filter(
94+
(file) => file.id !== upload.id
95+
);
96+
form.set("rag_uploads", this.uploadedFiles);
97+
}
98+
99+
@action
100+
openTestModal() {
101+
this.modal.show(AiToolTestModal, {
102+
model: {
103+
tool: this.args.editingModel,
104+
},
105+
});
106+
}
107+
108+
currentParameterSelection(data, index) {
109+
return data.parameters[index].type;
110+
}
111+
112+
get ragUploadsDescription() {
113+
return this.siteSettings.rag_images_enabled
114+
? i18n("discourse_ai.rag.uploads.description_with_images")
115+
: i18n("discourse_ai.rag.uploads.description");
116+
}
117+
118+
<template>
119+
<Form
120+
@onSubmit={{this.save}}
121+
@data={{this.formData}}
122+
class="ai-tool-editor"
123+
as |form data|
124+
>
125+
126+
{{! NAME }}
127+
<form.Field
128+
@name="name"
129+
@title={{i18n "discourse_ai.tools.name"}}
130+
@validation="required|length:1,100"
131+
@format="large"
132+
@tooltip={{i18n "discourse_ai.tools.name_help"}}
133+
as |field|
134+
>
135+
<field.Input class="ai-tool-editor__name" />
136+
</form.Field>
137+
138+
{{! TOOL NAME }}
139+
<form.Field
140+
@name="tool_name"
141+
@title={{i18n "discourse_ai.tools.tool_name"}}
142+
@validation="required|length:1,100"
143+
@format="large"
144+
@tooltip={{i18n "discourse_ai.tools.tool_name_help"}}
145+
as |field|
146+
>
147+
<field.Input class="ai-tool-editor__tool_name" />
148+
</form.Field>
149+
150+
{{! DESCRIPTION }}
151+
<form.Field
152+
@name="description"
153+
@title={{i18n "discourse_ai.tools.description"}}
154+
@validation="required|length:1,1000"
155+
@format="full"
156+
@tooltip={{i18n "discourse_ai.tools.description_help"}}
157+
as |field|
158+
>
159+
<field.Textarea
160+
@height={{60}}
161+
class="ai-tool-editor__description"
162+
placeholder={{i18n "discourse_ai.tools.description_help"}}
163+
/>
164+
</form.Field>
165+
166+
{{! SUMMARY }}
167+
<form.Field
168+
@name="summary"
169+
@title={{i18n "discourse_ai.tools.summary"}}
170+
@validation="required|length:1,255"
171+
@format="large"
172+
@tooltip={{i18n "discourse_ai.tools.summary_help"}}
173+
as |field|
174+
>
175+
<field.Input class="ai-tool-editor__summary" />
176+
</form.Field>
177+
178+
{{! PARAMETERS }}
179+
<form.Collection @name="parameters" as |collection index|>
180+
<div class="ai-tool-parameter">
181+
<form.Row as |row|>
182+
<row.Col @size={{6}}>
183+
<collection.Field
184+
@name="name"
185+
@title={{i18n "discourse_ai.tools.parameter_name"}}
186+
@validation="required|length:1,100"
187+
as |field|
188+
>
189+
<field.Input />
190+
</collection.Field>
191+
</row.Col>
192+
193+
<row.Col @size={{6}}>
194+
<collection.Field
195+
@name="type"
196+
@title={{i18n "discourse_ai.tools.parameter_type"}}
197+
@validation="required"
198+
as |field|
199+
>
200+
<field.Menu
201+
@selection={{this.currentParameterSelection data index}}
202+
as |menu|
203+
>
204+
{{#each this.PARAMETER_TYPES as |type|}}
205+
<menu.Item
206+
@value={{type.id}}
207+
data-type={{type.id}}
208+
>{{type.name}}</menu.Item>
209+
{{/each}}
210+
</field.Menu>
211+
</collection.Field>
212+
</row.Col>
213+
</form.Row>
214+
215+
<collection.Field
216+
@name="description"
217+
@title={{i18n "discourse_ai.tools.parameter_description"}}
218+
@validation="required|length:1,1000"
219+
as |field|
220+
>
221+
<field.Input class="ai-tool-editor__parameter-description" />
222+
</collection.Field>
223+
224+
<form.Row as |row|>
225+
<row.Col @size={{4}}>
226+
<collection.Field @name="required" @title="Required" as |field|>
227+
<field.Checkbox />
228+
</collection.Field>
229+
</row.Col>
230+
231+
<row.Col @size={{4}}>
232+
<collection.Field @name="enum" @title="Enum" as |field|>
233+
<field.Checkbox />
234+
</collection.Field>
235+
</row.Col>
236+
237+
<row.Col @size={{4}} class="ai-tool-parameter-actions">
238+
<form.Button
239+
@label="discourse_ai.tools.remove_parameter"
240+
@icon="trash-can"
241+
@action={{fn collection.remove index}}
242+
class="btn-danger"
243+
/>
244+
</row.Col>
245+
</form.Row>
246+
</div>
247+
</form.Collection>
248+
249+
<form.Button
250+
@icon="plus"
251+
@label="discourse_ai.tools.add_parameter"
252+
@action={{fn
253+
form.addItemToCollection
254+
"parameters"
255+
(hash name="" type="string" description="" required=false enum=false)
256+
}}
257+
/>
258+
259+
{{! SCRIPT }}
260+
<form.Field
261+
@name="script"
262+
@title={{i18n "discourse_ai.tools.script"}}
263+
@validation="required|length:1,100000"
264+
@format="full"
265+
as |field|
266+
>
267+
<field.Code @lang="javascript" @height={{600}} />
268+
</form.Field>
269+
270+
{{! Uploads }}
271+
{{#if this.siteSettings.ai_embeddings_enabled}}
272+
<form.Field
273+
@name="rag_uploads"
274+
@title={{i18n "discourse_ai.rag.uploads.title"}}
275+
@tooltip={{this.ragUploadsDescription}}
276+
as |field|
277+
>
278+
<field.Custom>
279+
<RagUploader
280+
@target={{@editingModel}}
281+
@updateUploads={{fn this.updateUploads form.addItemToCollection}}
282+
@onRemove={{fn this.removeUpload form}}
283+
@allowImages={{@settings.rag_images_enabled}}
284+
/>
285+
<RagOptions
286+
@model={{@editingModel}}
287+
@llms={{@llms}}
288+
@allowImages={{@settings.rag_images_enabled}}
289+
/>
290+
</field.Custom>
291+
</form.Field>
292+
{{/if}}
293+
294+
<form.Actions>
295+
{{#unless @isNew}}
296+
<form.Button
297+
@label="discourse_ai.tools.test"
298+
@action={{this.openTestModal}}
299+
class="ai-tool-editor__test-button"
300+
/>
301+
302+
<form.Button
303+
@label="discourse_ai.tools.delete"
304+
@icon="trash-can"
305+
@action={{this.delete}}
306+
class="btn-danger ai-tool-editor__delete"
307+
/>
308+
{{/unless}}
309+
310+
<form.Submit
311+
@label="discourse_ai.tools.save"
312+
class="ai-tool-editor__save"
313+
/>
314+
</form.Actions>
315+
</Form>
316+
</template>
317+
}

0 commit comments

Comments
 (0)