FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
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 didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
|
|
|
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
|
|
|
|
import { later } from "@ember/runloop";
|
|
|
|
import { inject as service } from "@ember/service";
|
|
|
|
import DButton from "discourse/components/d-button";
|
|
|
|
import Textarea from "discourse/components/d-textarea";
|
|
|
|
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
|
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
|
|
import Group from "discourse/models/group";
|
|
|
|
import I18n from "discourse-i18n";
|
|
|
|
import GroupChooser from "select-kit/components/group-chooser";
|
|
|
|
import DTooltip from "float-kit/components/d-tooltip";
|
|
|
|
import AiCommandSelector from "./ai-command-selector";
|
2023-12-07 16:42:56 -05:00
|
|
|
import AiPersonaCommandOptions from "./ai-persona-command-options";
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
|
|
|
|
export default class PersonaEditor extends Component {
|
|
|
|
@service router;
|
|
|
|
@service store;
|
|
|
|
@service dialog;
|
|
|
|
@service toasts;
|
|
|
|
|
|
|
|
@tracked allGroups = [];
|
|
|
|
@tracked isSaving = false;
|
|
|
|
@tracked editingModel = null;
|
|
|
|
@tracked showDelete = false;
|
|
|
|
|
|
|
|
@action
|
|
|
|
updateModel() {
|
|
|
|
this.editingModel = this.args.model.workingCopy();
|
|
|
|
this.showDelete = !this.args.model.isNew && !this.args.model.system;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
async updateAllGroups() {
|
|
|
|
this.allGroups = await Group.findAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
async save() {
|
|
|
|
const isNew = this.args.model.isNew;
|
|
|
|
this.isSaving = true;
|
|
|
|
|
|
|
|
const backupModel = this.args.model.workingCopy();
|
|
|
|
|
|
|
|
this.args.model.setProperties(this.editingModel);
|
|
|
|
try {
|
|
|
|
await this.args.model.save();
|
|
|
|
this.#sortPersonas();
|
|
|
|
if (isNew) {
|
|
|
|
this.args.personas.addObject(this.args.model);
|
|
|
|
this.router.transitionTo(
|
|
|
|
"adminPlugins.discourse-ai.ai-personas.show",
|
|
|
|
this.args.model
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.toasts.success({
|
|
|
|
data: { message: I18n.t("discourse_ai.ai_persona.saved") },
|
|
|
|
duration: 2000,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
this.args.model.setProperties(backupModel);
|
|
|
|
popupAjaxError(e);
|
|
|
|
} finally {
|
|
|
|
later(() => {
|
|
|
|
this.isSaving = false;
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
delete() {
|
|
|
|
return this.dialog.confirm({
|
|
|
|
message: I18n.t("discourse_ai.ai_persona.confirm_delete"),
|
|
|
|
didConfirm: () => {
|
|
|
|
return this.args.model.destroyRecord().then(() => {
|
|
|
|
this.args.personas.removeObject(this.args.model);
|
|
|
|
this.router.transitionTo(
|
|
|
|
"adminPlugins.discourse-ai.ai-personas.index"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
updateAllowedGroups(ids) {
|
|
|
|
this.editingModel.set("allowed_group_ids", ids);
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
async toggleEnabled() {
|
|
|
|
this.args.model.set("enabled", !this.args.model.enabled);
|
2023-11-26 21:01:05 -05:00
|
|
|
this.editingModel.set("enabled", this.args.model.enabled);
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
if (!this.args.model.isNew) {
|
|
|
|
try {
|
|
|
|
await this.args.model.update({ enabled: this.args.model.enabled });
|
|
|
|
} catch (e) {
|
|
|
|
popupAjaxError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
async togglePriority() {
|
|
|
|
this.args.model.set("priority", !this.args.model.priority);
|
2023-11-26 21:01:05 -05:00
|
|
|
this.editingModel.set("priority", this.args.model.priority);
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
if (!this.args.model.isNew) {
|
|
|
|
try {
|
|
|
|
await this.args.model.update({ priority: this.args.model.priority });
|
|
|
|
|
|
|
|
this.#sortPersonas();
|
|
|
|
} catch (e) {
|
|
|
|
popupAjaxError(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#sortPersonas() {
|
|
|
|
const sorted = this.args.personas.toArray().sort((a, b) => {
|
|
|
|
if (a.priority && !b.priority) {
|
|
|
|
return -1;
|
|
|
|
} else if (!a.priority && b.priority) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return a.name.localeCompare(b.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.args.personas.clear();
|
|
|
|
this.args.personas.setObjects(sorted);
|
|
|
|
}
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<form
|
|
|
|
class="form-horizontal ai-persona-editor"
|
|
|
|
{{didUpdate this.updateModel @model.id}}
|
|
|
|
{{didInsert this.updateModel @model.id}}
|
|
|
|
{{didInsert this.updateAllGroups @model.id}}
|
|
|
|
>
|
|
|
|
<div class="control-group">
|
|
|
|
<DToggleSwitch
|
|
|
|
class="ai-persona-editor__enabled"
|
|
|
|
@state={{@model.enabled}}
|
|
|
|
@label="discourse_ai.ai_persona.enabled"
|
|
|
|
{{on "click" this.toggleEnabled}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group ai-persona-editor__priority">
|
|
|
|
<DToggleSwitch
|
|
|
|
class="ai-persona-editor__priority"
|
|
|
|
@state={{@model.priority}}
|
|
|
|
@label="discourse_ai.ai_persona.priority"
|
|
|
|
{{on "click" this.togglePriority}}
|
|
|
|
/>
|
|
|
|
<DTooltip
|
|
|
|
@icon="question-circle"
|
2023-12-07 16:42:56 -05:00
|
|
|
@content={{I18n.t "discourse_ai.ai_persona.priority_help"}}
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
|
|
<label>{{I18n.t "discourse_ai.ai_persona.name"}}</label>
|
|
|
|
<Input
|
|
|
|
class="ai-persona-editor__name"
|
|
|
|
@type="text"
|
|
|
|
@value={{this.editingModel.name}}
|
|
|
|
disabled={{this.editingModel.system}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
|
|
<label>{{I18n.t "discourse_ai.ai_persona.description"}}</label>
|
|
|
|
<Textarea
|
|
|
|
class="ai-persona-editor__description"
|
|
|
|
@value={{this.editingModel.description}}
|
|
|
|
disabled={{this.editingModel.system}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
|
|
<label>{{I18n.t "discourse_ai.ai_persona.commands"}}</label>
|
|
|
|
<AiCommandSelector
|
|
|
|
class="ai-persona-editor__commands"
|
|
|
|
@value={{this.editingModel.commands}}
|
|
|
|
@disabled={{this.editingModel.system}}
|
|
|
|
@commands={{@personas.resultSetMeta.commands}}
|
|
|
|
/>
|
|
|
|
</div>
|
2023-12-07 16:42:56 -05:00
|
|
|
{{#unless this.editingModel.system}}
|
|
|
|
<AiPersonaCommandOptions
|
|
|
|
@persona={{this.editingModel}}
|
|
|
|
@commands={{this.editingModel.commands}}
|
|
|
|
@allCommands={{@personas.resultSetMeta.commands}}
|
|
|
|
/>
|
|
|
|
{{/unless}}
|
FEATURE: UI to update ai personas on admin page (#290)
Introduces a UI to manage customizable personas (admin only feature)
Part of the change was some extensive internal refactoring:
- AIBot now has a persona set in the constructor, once set it never changes
- Command now takes in bot as a constructor param, so it has the correct persona and is not generating AIBot objects on the fly
- Added a .prettierignore file, due to the way ALE is configured in nvim it is a pre-req for prettier to work
- Adds a bunch of validations on the AIPersona model, system personas (artist/creative etc...) are all seeded. We now ensure
- name uniqueness, and only allow certain properties to be touched for system personas.
- (JS note) the client side design takes advantage of nested routes, the parent route for personas gets all the personas via this.store.findAll("ai-persona") then child routes simply reach into this model to find a particular persona.
- (JS note) data is sideloaded into the ai-persona model the meta property supplied from the controller, resultSetMeta
- This removes ai_bot_enabled_personas and ai_bot_enabled_chat_commands, both should be controlled from the UI on a per persona basis
- Fixes a long standing bug in token accounting ... we were doing to_json.length instead of to_json.to_s.length
- Amended it so {commands} are always inserted at the end unconditionally, no need to add it to the template of the system message as it just confuses things
- Adds a concept of required_commands to stock personas, these are commands that must be configured for this stock persona to show up.
- Refactored tests so we stop requiring inference_stubs, it was very confusing to need it, added to plugin.rb for now which at least is clearer
- Migrates the persona selector to gjs
---------
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: Martin Brennan <martin@discourse.org>
2023-11-21 00:56:43 -05:00
|
|
|
<div class="control-group">
|
|
|
|
<label>{{I18n.t "discourse_ai.ai_persona.allowed_groups"}}</label>
|
|
|
|
<GroupChooser
|
|
|
|
@value={{this.editingModel.allowed_group_ids}}
|
|
|
|
@content={{this.allGroups}}
|
|
|
|
@onChange={{this.updateAllowedGroups}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
|
|
<label for="ai-persona-editor__system_prompt">{{I18n.t
|
|
|
|
"discourse_ai.ai_persona.system_prompt"
|
|
|
|
}}</label>
|
|
|
|
<Textarea
|
|
|
|
class="ai-persona-editor__system_prompt"
|
|
|
|
@value={{this.editingModel.system_prompt}}
|
|
|
|
disabled={{this.editingModel.system}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="control-group ai-persona-editor__action_panel">
|
|
|
|
<DButton
|
|
|
|
class="btn-primary ai-persona-editor__save"
|
|
|
|
@action={{this.save}}
|
|
|
|
@disabled={{this.isSaving}}
|
|
|
|
>{{I18n.t "discourse_ai.ai_persona.save"}}</DButton>
|
|
|
|
{{#if this.showDelete}}
|
|
|
|
<DButton
|
|
|
|
@action={{this.delete}}
|
|
|
|
class="btn-danger ai-persona-editor__delete"
|
|
|
|
>
|
|
|
|
{{I18n.t "discourse_ai.ai_persona.delete"}}
|
|
|
|
</DButton>
|
|
|
|
{{/if}}
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</template>
|
|
|
|
}
|