FEATURE: Add breadcrumbs to LLMs and Persona admin pages (#666)

Followup to https://github.com/discourse/discourse-ai/pull/656,
adding these back in with the new core component.
This commit is contained in:
Martin Brennan 2024-07-10 10:56:13 +10:00 committed by GitHub
parent 84b1c9af71
commit da6d70da8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 133 additions and 87 deletions

View File

@ -3,6 +3,8 @@ import { concat, 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 { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import DToggleSwitch from "discourse/components/d-toggle-switch"; import DToggleSwitch from "discourse/components/d-toggle-switch";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import icon from "discourse-common/helpers/d-icon"; import icon from "discourse-common/helpers/d-icon";
@ -11,6 +13,8 @@ import I18n from "discourse-i18n";
import AiLlmEditor from "./ai-llm-editor"; import AiLlmEditor from "./ai-llm-editor";
export default class AiLlmsListEditor extends Component { export default class AiLlmsListEditor extends Component {
@service adminPluginNavManager;
get hasLLMElements() { get hasLLMElements() {
return this.args.llms.length !== 0; return this.args.llms.length !== 0;
} }
@ -31,7 +35,12 @@ export default class AiLlmsListEditor extends Component {
} }
<template> <template>
<DBreadcrumbsItem
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-llms"
@label={{i18n "discourse_ai.llms.short_title"}}
/>
<section class="ai-llms-list-editor admin-detail pull-left"> <section class="ai-llms-list-editor admin-detail pull-left">
{{#if @currentLlm}} {{#if @currentLlm}}
<AiLlmEditor @model={{@currentLlm}} @llms={{@llms}} /> <AiLlmEditor @model={{@currentLlm}} @llms={{@llms}} />
{{else}} {{else}}

View File

@ -4,6 +4,8 @@ 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 { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import DToggleSwitch from "discourse/components/d-toggle-switch"; import DToggleSwitch from "discourse/components/d-toggle-switch";
import concatClass from "discourse/helpers/concat-class"; import concatClass from "discourse/helpers/concat-class";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@ -14,6 +16,7 @@ import I18n from "discourse-i18n";
import AiPersonaEditor from "./ai-persona-editor"; import AiPersonaEditor from "./ai-persona-editor";
export default class AiPersonaListEditor extends Component { export default class AiPersonaListEditor extends Component {
@service adminPluginNavManager;
@tracked _noPersonaText = null; @tracked _noPersonaText = null;
get noPersonaText() { get noPersonaText() {
@ -42,6 +45,10 @@ export default class AiPersonaListEditor extends Component {
} }
<template> <template>
<DBreadcrumbsItem
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-personas"
@label={{i18n "discourse_ai.ai_persona.short_title"}}
/>
<section class="ai-persona-list-editor__current admin-detail pull-left"> <section class="ai-persona-list-editor__current admin-detail pull-left">
{{#if @currentPersona}} {{#if @currentPersona}}
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} /> <AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />

View File

@ -1,8 +1,19 @@
import Component from "@glimmer/component";
import { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
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> export default class AiToolListEditor extends Component {
@service adminPluginNavManager;
<template>
<DBreadcrumbsItem
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-tools"
@label={{i18n "discourse_ai.tools.short_title"}}
/>
<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.t "discourse_ai.tools.short_title"}}</h3> <h3>{{I18n.t "discourse_ai.tools.short_title"}}</h3>
@ -43,4 +54,5 @@ import I18n from "discourse-i18n";
</tbody> </tbody>
</table> </table>
</section> </section>
</template> </template>
}

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
RSpec.describe "Admin AI persona configuration", type: :system, js: true do
fab!(:admin)
before do
SiteSetting.ai_bot_enabled = true
SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
sign_in(admin)
end
it "allows creation of a persona" do
visit "/admin/plugins/discourse-ai/ai-personas"
find(".ai-persona-list-editor__header .btn-primary").click()
find(".ai-persona-editor__name").set("Test Persona")
find(".ai-persona-editor__description").fill_in(with: "I am a test persona")
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
tool_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__tools")
tool_selector.expand
tool_selector.select_row_by_value("Read")
find(".ai-persona-editor__save").click()
expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new")
persona_id = page.current_path.split("/").last.to_i
persona = AiPersona.find(persona_id)
expect(persona.name).to eq("Test Persona")
expect(persona.description).to eq("I am a test persona")
expect(persona.system_prompt).to eq("You are a helpful bot")
expect(persona.tools).to eq([["Read", { "read_private" => nil }]])
end
it "will not allow deletion or editing of system personas" do
visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}"
expect(page).not_to have_selector(".ai-persona-editor__delete")
expect(find(".ai-persona-editor__system_prompt")).to be_disabled
end
it "will enable persona right away when you click on enable but does not save side effects" do
persona = Fabricate(:ai_persona, enabled: false)
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}"
find(".ai-persona-editor__name").set("Test Persona 1")
PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle
try_until_success { expect(persona.reload.enabled).to eq(true) }
persona.reload
expect(persona.enabled).to eq(true)
expect(persona.name).not_to eq("Test Persona 1")
end
it "can navigate the AI plugin with breadcrumbs" do
visit "/admin/plugins/discourse-ai/ai-personas"
expect(page).to have_css(".d-breadcrumbs")
expect(page).to have_css(".d-breadcrumbs__item", count: 4)
find(".d-breadcrumbs__item", text: I18n.t("admin_js.admin.plugins.title")).click
expect(page).to have_current_path("/admin/plugins")
end
end

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe "AI personas", type: :system, js: true do RSpec.describe "AI personas", type: :system, js: true do
fab!(:admin) fab!(:admin)
fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") } fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") }
@ -7,8 +8,6 @@ RSpec.describe "AI personas", type: :system, js: true do
SiteSetting.ai_bot_enabled = true SiteSetting.ai_bot_enabled = true
toggle_enabled_bots(bots: [gpt_4]) toggle_enabled_bots(bots: [gpt_4])
sign_in(admin) sign_in(admin)
Group.refresh_automatic_groups!
end end
it "remembers the last selected persona" do it "remembers the last selected persona" do
@ -31,49 +30,4 @@ RSpec.describe "AI personas", type: :system, js: true do
persona_selector.expand persona_selector.expand
expect(persona_selector).to have_selected_value(-2) expect(persona_selector).to have_selected_value(-2)
end end
it "allows creation of a persona" do
visit "/admin/plugins/discourse-ai/ai-personas"
find(".ai-persona-list-editor__header .btn-primary").click()
find(".ai-persona-editor__name").set("Test Persona")
find(".ai-persona-editor__description").fill_in(with: "I am a test persona")
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
tool_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__tools")
tool_selector.expand
tool_selector.select_row_by_value("Read")
find(".ai-persona-editor__save").click()
expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new")
persona_id = page.current_path.split("/").last.to_i
persona = AiPersona.find(persona_id)
expect(persona.name).to eq("Test Persona")
expect(persona.description).to eq("I am a test persona")
expect(persona.system_prompt).to eq("You are a helpful bot")
expect(persona.tools).to eq([["Read", { "read_private" => nil }]])
end
it "will not allow deletion or editing of system personas" do
visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}"
expect(page).not_to have_selector(".ai-persona-editor__delete")
expect(find(".ai-persona-editor__system_prompt")).to be_disabled
end
it "will enable persona right away when you click on enable but does not save side effects" do
persona = Fabricate(:ai_persona, enabled: false)
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}"
find(".ai-persona-editor__name").set("Test Persona 1")
PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle
try_until_success { expect(persona.reload.enabled).to eq(true) }
persona.reload
expect(persona.enabled).to eq(true)
expect(persona.name).not_to eq("Test Persona 1")
end
end end