Skip to content

Commit da6d70d

Browse files
FEATURE: Add breadcrumbs to LLMs and Persona admin pages (#666)
Followup to #656, adding these back in with the new core component.
1 parent 84b1c9a commit da6d70d

File tree

5 files changed

+133
-87
lines changed

5 files changed

+133
-87
lines changed

assets/javascripts/discourse/components/ai-llms-list-editor.gjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { concat, fn } from "@ember/helper";
33
import { on } from "@ember/modifier";
44
import { action } from "@ember/object";
55
import { LinkTo } from "@ember/routing";
6+
import { inject as service } from "@ember/service";
7+
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
68
import DToggleSwitch from "discourse/components/d-toggle-switch";
79
import { popupAjaxError } from "discourse/lib/ajax-error";
810
import icon from "discourse-common/helpers/d-icon";
@@ -11,6 +13,8 @@ import I18n from "discourse-i18n";
1113
import AiLlmEditor from "./ai-llm-editor";
1214

1315
export default class AiLlmsListEditor extends Component {
16+
@service adminPluginNavManager;
17+
1418
get hasLLMElements() {
1519
return this.args.llms.length !== 0;
1620
}
@@ -31,7 +35,12 @@ export default class AiLlmsListEditor extends Component {
3135
}
3236

3337
<template>
38+
<DBreadcrumbsItem
39+
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-llms"
40+
@label={{i18n "discourse_ai.llms.short_title"}}
41+
/>
3442
<section class="ai-llms-list-editor admin-detail pull-left">
43+
3544
{{#if @currentLlm}}
3645
<AiLlmEditor @model={{@currentLlm}} @llms={{@llms}} />
3746
{{else}}

assets/javascripts/discourse/components/ai-persona-list-editor.gjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { fn } from "@ember/helper";
44
import { on } from "@ember/modifier";
55
import { action } from "@ember/object";
66
import { LinkTo } from "@ember/routing";
7+
import { inject as service } from "@ember/service";
8+
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
79
import DToggleSwitch from "discourse/components/d-toggle-switch";
810
import concatClass from "discourse/helpers/concat-class";
911
import { popupAjaxError } from "discourse/lib/ajax-error";
@@ -14,6 +16,7 @@ import I18n from "discourse-i18n";
1416
import AiPersonaEditor from "./ai-persona-editor";
1517

1618
export default class AiPersonaListEditor extends Component {
19+
@service adminPluginNavManager;
1720
@tracked _noPersonaText = null;
1821

1922
get noPersonaText() {
@@ -42,6 +45,10 @@ export default class AiPersonaListEditor extends Component {
4245
}
4346

4447
<template>
48+
<DBreadcrumbsItem
49+
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-personas"
50+
@label={{i18n "discourse_ai.ai_persona.short_title"}}
51+
/>
4552
<section class="ai-persona-list-editor__current admin-detail pull-left">
4653
{{#if @currentPersona}}
4754
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />
Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,58 @@
1+
import Component from "@glimmer/component";
12
import { LinkTo } from "@ember/routing";
3+
import { inject as service } from "@ember/service";
4+
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
25
import icon from "discourse-common/helpers/d-icon";
6+
import i18n from "discourse-common/helpers/i18n";
37
import I18n from "discourse-i18n";
48

5-
<template>
6-
<section class="ai-tool-list-editor__current admin-detail pull-left">
7-
<div class="ai-tool-list-editor__header">
8-
<h3>{{I18n.t "discourse_ai.tools.short_title"}}</h3>
9-
<LinkTo
10-
@route="adminPlugins.show.discourse-ai-tools.new"
11-
class="btn btn-small btn-primary ai-tool-list-editor__new-button"
12-
>
13-
{{icon "plus"}}
14-
<span>{{I18n.t "discourse_ai.tools.new"}}</span>
15-
</LinkTo>
16-
</div>
9+
export default class AiToolListEditor extends Component {
10+
@service adminPluginNavManager;
1711

18-
<table class="content-list ai-tool-list-editor">
19-
<tbody>
20-
{{#each @tools as |tool|}}
21-
<tr data-tool-id={{tool.id}} class="ai-tool-list__row">
22-
<td>
23-
<div class="ai-tool-list__name-with-description">
24-
<div class="ai-tool-list__name">
25-
<strong>
26-
{{tool.name}}
27-
</strong>
28-
</div>
29-
<div class="ai-tool-list__description">
30-
{{tool.description}}
12+
<template>
13+
<DBreadcrumbsItem
14+
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-tools"
15+
@label={{i18n "discourse_ai.tools.short_title"}}
16+
/>
17+
<section class="ai-tool-list-editor__current admin-detail pull-left">
18+
<div class="ai-tool-list-editor__header">
19+
<h3>{{I18n.t "discourse_ai.tools.short_title"}}</h3>
20+
<LinkTo
21+
@route="adminPlugins.show.discourse-ai-tools.new"
22+
class="btn btn-small btn-primary ai-tool-list-editor__new-button"
23+
>
24+
{{icon "plus"}}
25+
<span>{{I18n.t "discourse_ai.tools.new"}}</span>
26+
</LinkTo>
27+
</div>
28+
29+
<table class="content-list ai-tool-list-editor">
30+
<tbody>
31+
{{#each @tools as |tool|}}
32+
<tr data-tool-id={{tool.id}} class="ai-tool-list__row">
33+
<td>
34+
<div class="ai-tool-list__name-with-description">
35+
<div class="ai-tool-list__name">
36+
<strong>
37+
{{tool.name}}
38+
</strong>
39+
</div>
40+
<div class="ai-tool-list__description">
41+
{{tool.description}}
42+
</div>
3143
</div>
32-
</div>
33-
</td>
34-
<td>
35-
<LinkTo
36-
@route="adminPlugins.show.discourse-ai-tools.show"
37-
@model={{tool}}
38-
class="btn btn-text btn-small"
39-
>{{I18n.t "discourse_ai.tools.edit"}}</LinkTo>
40-
</td>
41-
</tr>
42-
{{/each}}
43-
</tbody>
44-
</table>
45-
</section>
46-
</template>
44+
</td>
45+
<td>
46+
<LinkTo
47+
@route="adminPlugins.show.discourse-ai-tools.show"
48+
@model={{tool}}
49+
class="btn btn-text btn-small"
50+
>{{I18n.t "discourse_ai.tools.edit"}}</LinkTo>
51+
</td>
52+
</tr>
53+
{{/each}}
54+
</tbody>
55+
</table>
56+
</section>
57+
</template>
58+
}

spec/system/admin_ai_persona_spec.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe "Admin AI persona configuration", type: :system, js: true do
4+
fab!(:admin)
5+
6+
before do
7+
SiteSetting.ai_bot_enabled = true
8+
SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
9+
sign_in(admin)
10+
end
11+
12+
it "allows creation of a persona" do
13+
visit "/admin/plugins/discourse-ai/ai-personas"
14+
find(".ai-persona-list-editor__header .btn-primary").click()
15+
find(".ai-persona-editor__name").set("Test Persona")
16+
find(".ai-persona-editor__description").fill_in(with: "I am a test persona")
17+
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
18+
19+
tool_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__tools")
20+
tool_selector.expand
21+
tool_selector.select_row_by_value("Read")
22+
23+
find(".ai-persona-editor__save").click()
24+
25+
expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new")
26+
27+
persona_id = page.current_path.split("/").last.to_i
28+
29+
persona = AiPersona.find(persona_id)
30+
expect(persona.name).to eq("Test Persona")
31+
expect(persona.description).to eq("I am a test persona")
32+
expect(persona.system_prompt).to eq("You are a helpful bot")
33+
expect(persona.tools).to eq([["Read", { "read_private" => nil }]])
34+
end
35+
36+
it "will not allow deletion or editing of system personas" do
37+
visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}"
38+
expect(page).not_to have_selector(".ai-persona-editor__delete")
39+
expect(find(".ai-persona-editor__system_prompt")).to be_disabled
40+
end
41+
42+
it "will enable persona right away when you click on enable but does not save side effects" do
43+
persona = Fabricate(:ai_persona, enabled: false)
44+
45+
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}"
46+
47+
find(".ai-persona-editor__name").set("Test Persona 1")
48+
PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle
49+
50+
try_until_success { expect(persona.reload.enabled).to eq(true) }
51+
52+
persona.reload
53+
expect(persona.enabled).to eq(true)
54+
expect(persona.name).not_to eq("Test Persona 1")
55+
end
56+
57+
it "can navigate the AI plugin with breadcrumbs" do
58+
visit "/admin/plugins/discourse-ai/ai-personas"
59+
expect(page).to have_css(".d-breadcrumbs")
60+
expect(page).to have_css(".d-breadcrumbs__item", count: 4)
61+
find(".d-breadcrumbs__item", text: I18n.t("admin_js.admin.plugins.title")).click
62+
expect(page).to have_current_path("/admin/plugins")
63+
end
64+
end

spec/system/ai_bot/persona_spec.rb

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# frozen_string_literal: true
2+
23
RSpec.describe "AI personas", type: :system, js: true do
34
fab!(:admin)
45
fab!(:gpt_4) { Fabricate(:llm_model, name: "gpt-4") }
@@ -7,8 +8,6 @@
78
SiteSetting.ai_bot_enabled = true
89
toggle_enabled_bots(bots: [gpt_4])
910
sign_in(admin)
10-
11-
Group.refresh_automatic_groups!
1211
end
1312

1413
it "remembers the last selected persona" do
@@ -31,49 +30,4 @@
3130
persona_selector.expand
3231
expect(persona_selector).to have_selected_value(-2)
3332
end
34-
35-
it "allows creation of a persona" do
36-
visit "/admin/plugins/discourse-ai/ai-personas"
37-
find(".ai-persona-list-editor__header .btn-primary").click()
38-
find(".ai-persona-editor__name").set("Test Persona")
39-
find(".ai-persona-editor__description").fill_in(with: "I am a test persona")
40-
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
41-
42-
tool_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__tools")
43-
tool_selector.expand
44-
tool_selector.select_row_by_value("Read")
45-
46-
find(".ai-persona-editor__save").click()
47-
48-
expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new")
49-
50-
persona_id = page.current_path.split("/").last.to_i
51-
52-
persona = AiPersona.find(persona_id)
53-
expect(persona.name).to eq("Test Persona")
54-
expect(persona.description).to eq("I am a test persona")
55-
expect(persona.system_prompt).to eq("You are a helpful bot")
56-
expect(persona.tools).to eq([["Read", { "read_private" => nil }]])
57-
end
58-
59-
it "will not allow deletion or editing of system personas" do
60-
visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}"
61-
expect(page).not_to have_selector(".ai-persona-editor__delete")
62-
expect(find(".ai-persona-editor__system_prompt")).to be_disabled
63-
end
64-
65-
it "will enable persona right away when you click on enable but does not save side effects" do
66-
persona = Fabricate(:ai_persona, enabled: false)
67-
68-
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}"
69-
70-
find(".ai-persona-editor__name").set("Test Persona 1")
71-
PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle
72-
73-
try_until_success { expect(persona.reload.enabled).to eq(true) }
74-
75-
persona.reload
76-
expect(persona.enabled).to eq(true)
77-
expect(persona.name).not_to eq("Test Persona 1")
78-
end
7933
end

0 commit comments

Comments
 (0)