FEATURE: Improve admin plugin UI and use new plugins show route (#512)

This commit changes Discourse AI's admin plugin page to use the new plugin
show route. The UI for persona editing has also been improved for consistency,
and other plugin UIs will follow suit:

Settings for the plugin are now listed in the plugin UI and can be changed
from there directly after core PR discourse/discourse#26154 is merged.

See also:

* https://github.com/discourse/discourse/pull/26024
* https://github.com/discourse/discourse/pull/26154
* https://github.com/discourse/discourse/pull/26254
This commit is contained in:
Martin Brennan 2024-03-21 14:29:56 +10:00 committed by GitHub
parent 5cac47a30a
commit fb0d56324f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 176 additions and 67 deletions

View File

@ -1,10 +1,10 @@
export default {
resource: "admin.adminPlugins",
resource: "admin.adminPlugins.show",
path: "/plugins",
map() {
this.route("discourse-ai", function () {
this.route("discourse-ai", { path: "/" }, function () {
this.route("ai-personas", function () {
this.route("new");
this.route("show", { path: "/:id" });

View File

@ -8,6 +8,7 @@ import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { LinkTo } from "@ember/routing";
import { later } from "@ember/runloop";
import { inject as service } from "@ember/service";
import BackButton from "discourse/components/back-button";
import DButton from "discourse/components/d-button";
import Textarea from "discourse/components/d-textarea";
import DToggleSwitch from "discourse/components/d-toggle-switch";
@ -58,7 +59,7 @@ export default class PersonaEditor extends Component {
if (isNew) {
this.args.personas.addObject(this.args.model);
this.router.transitionTo(
"adminPlugins.discourse-ai.ai-personas.show",
"adminPlugins.show.discourse-ai.ai-personas.show",
this.args.model
);
} else {
@ -109,7 +110,7 @@ export default class PersonaEditor extends Component {
return this.args.model.destroyRecord().then(() => {
this.args.personas.removeObject(this.args.model);
this.router.transitionTo(
"adminPlugins.discourse-ai.ai-personas.index"
"adminPlugins.show.discourse-ai.ai-personas.index"
);
});
},
@ -180,6 +181,10 @@ export default class PersonaEditor extends Component {
}
<template>
<BackButton
@route="adminPlugins.show.discourse-ai.ai-personas"
@label="discourse_ai.ai_persona.back"
/>
<form
class="form-horizontal ai-persona-editor"
{{didUpdate this.updateModel @model.id}}

View File

@ -1,9 +1,15 @@
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 { LinkTo } from "@ember/routing";
import DToggleSwitch from "discourse/components/d-toggle-switch";
import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { cook } from "discourse/lib/text";
import icon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import I18n from "discourse-i18n";
import AiPersonaEditor from "./ai-persona-editor";
@ -21,45 +27,93 @@ export default class AiPersonaListEditor extends Component {
return this._noPersonaText;
}
@action
async toggleEnabled(persona) {
const oldValue = persona.enabled;
const newValue = !oldValue;
try {
persona.set("enabled", newValue);
await persona.save();
} catch (err) {
persona.set("enabled", oldValue);
popupAjaxError(err);
}
}
<template>
<div class="ai-persona-list-editor__header">
<h3>{{I18n.t "discourse_ai.ai_persona.title"}}</h3>
{{#unless @currentPersona.isNew}}
<LinkTo
@route="adminPlugins.discourse-ai.ai-personas.new"
class="btn btn-primary"
>
{{icon "plus"}}
<span>{{I18n.t "discourse_ai.ai_persona.new"}}</span>
</LinkTo>
{{/unless}}
</div>
<div class="content-list ai-persona-list-editor">
<ul>
{{#each @personas as |persona|}}
<li
class={{concatClass
(if persona.enabled "" "diabled")
(if persona.priority "priority")
}}
>
<LinkTo
@route="adminPlugins.discourse-ai.ai-personas.show"
current-when="true"
@model={{persona}}
>{{persona.name}}
</LinkTo>
</li>
{{/each}}
</ul>
</div>
<section class="ai-persona-list-editor__current content-body">
<section class="ai-persona-list-editor__current admin-detail pull-left">
{{#if @currentPersona}}
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />
{{else}}
<div class="ai-persona-list-editor__empty">
{{this.noPersonaText}}
<div class="ai-persona-list-editor__header">
<h3>{{i18n "discourse_ai.ai_persona.short_title"}}</h3>
{{#unless @currentPersona.isNew}}
<LinkTo
@route="adminPlugins.show.discourse-ai.ai-personas.new"
class="btn btn-small btn-primary"
>
{{icon "plus"}}
<span>{{I18n.t "discourse_ai.ai_persona.new"}}</span>
</LinkTo>
{{/unless}}
</div>
<div class="ai-persona-list-editor__empty">
<details class="details__boxed">
<summary>{{i18n
"discourse_ai.ai_persona.what_are_personas"
}}</summary>
{{this.noPersonaText}}
</details>
</div>
<table class="content-list ai-persona-list-editor">
<thead>
<tr>
<th>{{i18n "discourse_ai.ai_persona.name"}}</th>
<th>{{i18n "discourse_ai.ai_persona.enabled"}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each @personas as |persona|}}
<tr
data-persona-id={{persona.id}}
class={{concatClass
"ai-persona-list__row"
(if persona.priority "priority")
}}
>
<td>
<div class="ai-persona-list__name-with-description">
<div class="ai-persona-list__name">
<strong>
{{persona.name}}
</strong>
</div>
<div class="ai-persona-list__description">
{{persona.description}}
</div>
</div>
</td>
<td>
<DToggleSwitch
@state={{persona.enabled}}
{{on "click" (fn this.toggleEnabled persona)}}
/>
</td>
<td>
<LinkTo
@route="adminPlugins.show.discourse-ai.ai-personas.show"
@model={{persona}}
class="btn btn-text btn-small"
>{{i18n "discourse_ai.ai_persona.edit"}} </LinkTo>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{/if}}
</section>
</template>

View File

@ -1,9 +0,0 @@
import { inject as service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
router: service(),
beforeModel() {
this.router.transitionTo("adminPlugins.discourse-ai.ai-personas");
},
});

View File

@ -2,6 +2,6 @@ import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
model() {
return this.modelFor("adminPlugins.discourse-ai.ai-personas");
return this.modelFor("adminPlugins.show.discourse-ai.ai-personas");
},
});

View File

@ -1,10 +1,10 @@
import { AUTO_GROUPS } from "discourse/lib/constants";
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
async model() {
const record = this.store.createRecord("ai-persona");
// TL0
record.set("allowed_group_ids", [10]);
record.set("allowed_group_ids", [AUTO_GROUPS.trust_level_0.id]);
return record;
},
@ -12,7 +12,7 @@ export default DiscourseRoute.extend({
this._super(controller, model);
controller.set(
"allPersonas",
this.modelFor("adminPlugins.discourse-ai.ai-personas")
this.modelFor("adminPlugins.show.discourse-ai.ai-personas")
);
},
});

View File

@ -2,7 +2,9 @@ import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
async model(params) {
const allPersonas = this.modelFor("adminPlugins.discourse-ai.ai-personas");
const allPersonas = this.modelFor(
"adminPlugins.show.discourse-ai.ai-personas"
);
const id = parseInt(params.id, 10);
return allPersonas.findBy("id", id);
},
@ -11,7 +13,7 @@ export default DiscourseRoute.extend({
this._super(controller, model);
controller.set(
"allPersonas",
this.modelFor("adminPlugins.discourse-ai.ai-personas")
this.modelFor("adminPlugins.show.discourse-ai.ai-personas")
);
},
});

View File

@ -1,7 +1,7 @@
import DiscourseRoute from "discourse/routes/discourse";
export default DiscourseRoute.extend({
async model() {
export default class DiscourseAiAiPersonasRoute extends DiscourseRoute {
model() {
return this.store.findAll("ai-persona");
},
});
}
}

View File

@ -0,0 +1,12 @@
import { inject as service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiIndexRoute extends DiscourseRoute {
@service router;
beforeModel() {
return this.router.transitionTo(
"adminPlugins.show.discourse-ai.ai-personas.index"
);
}
}

View File

@ -0,0 +1 @@
<AiPersonaListEditor @personas={{this.model}} />

View File

@ -0,0 +1 @@
<AiPersonaListEditor @personas={{this.model}} />

View File

@ -0,0 +1,26 @@
import { PLUGIN_NAV_MODE_TOP } from "discourse/lib/admin-plugin-config-nav";
import { withPluginApi } from "discourse/lib/plugin-api";
export default {
name: "discourse-ai-admin-plugin-configuration-nav",
initialize(container) {
const currentUser = container.lookup("service:current-user");
if (!currentUser || !currentUser.admin) {
return;
}
withPluginApi("1.1.0", (api) => {
api.addAdminPluginConfigurationNav("discourse-ai", PLUGIN_NAV_MODE_TOP, [
{
label: "admin.plugins.change_settings_short",
route: "adminPlugins.show.settings",
},
{
label: "discourse_ai.ai_persona.short_title",
route: "adminPlugins.show.discourse-ai.ai-personas",
},
]);
});
},
};

View File

@ -1,10 +1,19 @@
.admin-contents .ai-persona-list-editor {
margin-top: 0;
}
.ai-persona-list-editor {
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
margin: 0 0 1em 0;
h3 {
margin: 0;
}
}
&__current {
padding-left: 20px;
}

View File

@ -0,0 +1,7 @@
.ai-persona-editor {
&__system_prompt,
&__description,
.select-kit.multi-select {
width: 100%;
}
}

View File

@ -114,7 +114,9 @@ en:
select_option: "Select an option..."
ai_persona:
back: Back
name: Name
edit: Edit
description: Description
no_llm_selected: "No language model selected"
max_context_posts: "Max Context Posts"
@ -129,12 +131,13 @@ en:
system_prompt: System Prompt
save: Save
saved: AI Persona Saved
enabled: Enabled
enabled: "Enabled?"
commands: Enabled Commands
allowed_groups: Allowed Groups
confirm_delete: Are you sure you want to delete this persona?
new: New
new: "New Persona"
title: "AI Personas"
short_title: "Personas"
delete: Delete
temperature: Temperature
temperature_help: Temperature to use for the LLM, increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)
@ -143,16 +146,15 @@ en:
priority: Priority
priority_help: Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically.
command_options: "Command Options"
what_are_personas: "What are AI Personas?"
no_persona_selected: |
## What are AI Personas?
AI Personas are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more personalized and engaging user experience.
## Why use AI Personas?
#### Why use AI Personas?
With AI Personas, you can tailor the AI's behavior to better fit the context and tone of your forum. Whether you want the AI to be more formal for a professional setting, more casual for a community forum, or even embody a specific character for a role-playing game, AI Personas give you the flexibility to do so.
## Group-Specific Access to AI Personas
#### Group-Specific Access to AI Personas
Moreover, you can set it up so that certain user groups have access to specific personas. This means you can have different AI behaviors for different sections of your forum, further enhancing the diversity and richness of your community's interactions.

View File

@ -35,8 +35,6 @@ Discourse::Application.routes.draw do
:constraints => StaffConstraint.new
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
get "/", to: redirect("/admin/plugins/discourse-ai/ai-personas")
resources :ai_personas,
only: %i[index create show update destroy],
path: "ai-personas",

View File

@ -17,6 +17,7 @@ register_asset "stylesheets/modules/ai-helper/common/ai-helper.scss"
register_asset "stylesheets/modules/ai-bot/common/bot-replies.scss"
register_asset "stylesheets/modules/ai-bot/common/ai-persona.scss"
register_asset "stylesheets/modules/ai-bot/mobile/ai-persona.scss", :mobile
register_asset "stylesheets/modules/embeddings/common/semantic-related-topics.scss"
register_asset "stylesheets/modules/embeddings/common/semantic-search.scss"
@ -38,7 +39,7 @@ after_initialize do
require_relative "discourse_automation/llm_triage"
require_relative "discourse_automation/llm_report"
add_admin_route "discourse_ai.title", "discourse-ai"
add_admin_route("discourse_ai.title", "discourse-ai", { use_new_show_route: true })
[
DiscourseAi::Embeddings::EntryPoint.new,