UX: No admin header for edit personas tools or llms (#1021)

In this PR, we added functionality to hide the admin header for edit/new actions - https://github.com/discourse/discourse/pull/30175

To make it work properly, we have to rename `show` to `edit` which is also a more accurate name.
This commit is contained in:
Krzysztof Kotlarek 2024-12-12 10:48:58 +11:00 committed by GitHub
parent 47c1ea337e
commit 04c4ff8cf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 50 additions and 28 deletions

View File

@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiLlmsShow extends DiscourseRoute { export default class AdminPluginsShowDiscourseAiLlmsEdit extends DiscourseRoute {
async model(params) { async model(params) {
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms"); const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);

View File

@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiPersonasShow extends DiscourseRoute { export default class AdminPluginsShowDiscourseAiPersonasEdit extends DiscourseRoute {
async model(params) { async model(params) {
const allPersonas = this.modelFor( const allPersonas = this.modelFor(
"adminPlugins.show.discourse-ai-personas" "adminPlugins.show.discourse-ai-personas"

View File

@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiToolsShowRoute extends DiscourseRoute { export default class DiscourseAiToolsEditRoute extends DiscourseRoute {
async model(params) { async model(params) {
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools"); const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);

View File

@ -30,7 +30,10 @@ module DiscourseAi
} }
end end
def show def new
end
def edit
llm_model = LlmModel.find(params[:id]) llm_model = LlmModel.find(params[:id])
render json: LlmModelSerializer.new(llm_model) render json: LlmModelSerializer.new(llm_model)
end end

View File

@ -5,7 +5,7 @@ module DiscourseAi
class AiPersonasController < ::Admin::AdminController class AiPersonasController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME requires_plugin ::DiscourseAi::PLUGIN_NAME
before_action :find_ai_persona, only: %i[show update destroy create_user] before_action :find_ai_persona, only: %i[edit update destroy create_user]
def index def index
ai_personas = ai_personas =
@ -33,7 +33,10 @@ module DiscourseAi
render json: { ai_personas: ai_personas, meta: { tools: tools, llms: llms } } render json: { ai_personas: ai_personas, meta: { tools: tools, llms: llms } }
end end
def show def new
end
def edit
render json: LocalizedAiPersonaSerializer.new(@ai_persona) render json: LocalizedAiPersonaSerializer.new(@ai_persona)
end end

View File

@ -5,14 +5,17 @@ module DiscourseAi
class AiToolsController < ::Admin::AdminController class AiToolsController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME requires_plugin ::DiscourseAi::PLUGIN_NAME
before_action :find_ai_tool, only: %i[test show update destroy] before_action :find_ai_tool, only: %i[test edit update destroy]
def index def index
ai_tools = AiTool.all ai_tools = AiTool.all
render_serialized({ ai_tools: ai_tools }, AiCustomToolListSerializer, root: false) render_serialized({ ai_tools: ai_tools }, AiCustomToolListSerializer, root: false)
end end
def show def new
end
def edit
render_serialized(@ai_tool, AiCustomToolSerializer) render_serialized(@ai_tool, AiCustomToolSerializer)
end end

View File

@ -6,17 +6,17 @@ export default {
map() { map() {
this.route("discourse-ai-personas", { path: "ai-personas" }, function () { this.route("discourse-ai-personas", { path: "ai-personas" }, function () {
this.route("new"); this.route("new");
this.route("show", { path: "/:id" }); this.route("edit", { path: "/:id/edit" });
}); });
this.route("discourse-ai-llms", { path: "ai-llms" }, function () { this.route("discourse-ai-llms", { path: "ai-llms" }, function () {
this.route("new"); this.route("new");
this.route("show", { path: "/:id" }); this.route("edit", { path: "/:id/edit" });
}); });
this.route("discourse-ai-tools", { path: "ai-tools" }, function () { this.route("discourse-ai-tools", { path: "ai-tools" }, function () {
this.route("new"); this.route("new");
this.route("show", { path: "/:id" }); this.route("edit", { path: "/:id/edit" });
}); });
this.route("discourse-ai-spam", { path: "ai-spam" }); this.route("discourse-ai-spam", { path: "ai-spam" });
this.route("discourse-ai-usage", { path: "ai-usage" }); this.route("discourse-ai-usage", { path: "ai-usage" });

View File

@ -167,7 +167,7 @@ export default class AiLlmsListEditor extends Component {
</td> </td>
<td class="d-admin-row__controls"> <td class="d-admin-row__controls">
<LinkTo <LinkTo
@route="adminPlugins.show.discourse-ai-llms.show" @route="adminPlugins.show.discourse-ai-llms.edit"
class="btn btn-default btn-small ai-llm-list__edit-button" class="btn btn-default btn-small ai-llm-list__edit-button"
@model={{llm.id}} @model={{llm.id}}
> >

View File

@ -135,7 +135,7 @@ export default class PersonaEditor extends Component {
if (isNew && this.args.model.rag_uploads.length === 0) { if (isNew && this.args.model.rag_uploads.length === 0) {
this.args.personas.addObject(this.args.model); this.args.personas.addObject(this.args.model);
this.router.transitionTo( this.router.transitionTo(
"adminPlugins.show.discourse-ai-personas.show", "adminPlugins.show.discourse-ai-personas.edit",
this.args.model this.args.model
); );
} else { } else {

View File

@ -92,7 +92,7 @@ export default class AiPersonaListEditor extends Component {
</td> </td>
<td class="d-admin-row__controls"> <td class="d-admin-row__controls">
<LinkTo <LinkTo
@route="adminPlugins.show.discourse-ai-personas.show" @route="adminPlugins.show.discourse-ai-personas.edit"
@model={{persona}} @model={{persona}}
class="btn btn-text btn-small" class="btn btn-text btn-small"
>{{i18n "discourse_ai.ai_persona.edit"}} </LinkTo> >{{i18n "discourse_ai.ai_persona.edit"}} </LinkTo>

View File

@ -103,7 +103,7 @@ export default class AiToolEditor extends Component {
} }
this.router.transitionTo( this.router.transitionTo(
"adminPlugins.show.discourse-ai-tools.show", "adminPlugins.show.discourse-ai-tools.edit",
this.args.model this.args.model
); );
} catch (e) { } catch (e) {

View File

@ -57,7 +57,7 @@ export default class AiToolListEditor extends Component {
</td> </td>
<td class="d-admin-row__controls"> <td class="d-admin-row__controls">
<LinkTo <LinkTo
@route="adminPlugins.show.discourse-ai-tools.show" @route="adminPlugins.show.discourse-ai-tools.edit"
@model={{tool}} @model={{tool}}
class="btn btn-text btn-small" class="btn btn-text btn-small"
>{{I18n.t "discourse_ai.tools.edit"}}</LinkTo> >{{I18n.t "discourse_ai.tools.edit"}}</LinkTo>

View File

@ -53,7 +53,7 @@ Discourse::Application.routes.draw do
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
resources :ai_personas, resources :ai_personas,
only: %i[index create show update destroy], only: %i[index new create edit update destroy],
path: "ai-personas", path: "ai-personas",
controller: "discourse_ai/admin/ai_personas" controller: "discourse_ai/admin/ai_personas"
@ -61,7 +61,7 @@ Discourse::Application.routes.draw do
resources( resources(
:ai_tools, :ai_tools,
only: %i[index create show update destroy], only: %i[index new create edit update destroy],
path: "ai-tools", path: "ai-tools",
controller: "discourse_ai/admin/ai_tools", controller: "discourse_ai/admin/ai_tools",
) )
@ -85,7 +85,7 @@ Discourse::Application.routes.draw do
post "/ai-spam/test", to: "discourse_ai/admin/ai_spam#test" post "/ai-spam/test", to: "discourse_ai/admin/ai_spam#test"
resources :ai_llms, resources :ai_llms,
only: %i[index create show update destroy], only: %i[index new create edit update destroy],
path: "ai-llms", path: "ai-llms",
controller: "discourse_ai/admin/ai_llms" do controller: "discourse_ai/admin/ai_llms" do
collection { get :test } collection { get :test }

View File

@ -141,9 +141,9 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
end end
end end
describe "GET #show" do describe "GET #edit" do
it "returns a success response" do it "returns a success response" do
get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json" get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}/edit.json"
expect(response).to be_successful expect(response).to be_successful
expect(response.parsed_body["ai_persona"]["name"]).to eq(ai_persona.name) expect(response.parsed_body["ai_persona"]["name"]).to eq(ai_persona.name)
end end
@ -152,7 +152,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
upload = Fabricate(:upload) upload = Fabricate(:upload)
RagDocumentFragment.link_target_and_uploads(ai_persona, [upload.id]) RagDocumentFragment.link_target_and_uploads(ai_persona, [upload.id])
get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}.json" get "/admin/plugins/discourse-ai/ai-personas/#{ai_persona.id}/edit.json"
expect(response).to be_successful expect(response).to be_successful
serialized_persona = response.parsed_body["ai_persona"] serialized_persona = response.parsed_body["ai_persona"]

View File

@ -35,9 +35,9 @@ RSpec.describe DiscourseAi::Admin::AiToolsController do
end end
end end
describe "GET #show" do describe "GET #edit" do
it "returns a success response" do it "returns a success response" do
get "/admin/plugins/discourse-ai/ai-tools/#{ai_tool.id}.json" get "/admin/plugins/discourse-ai/ai-tools/#{ai_tool.id}/edit.json"
expect(response).to be_successful expect(response).to be_successful
expect(response.parsed_body["ai_tool"]["name"]).to eq(ai_tool.name) expect(response.parsed_body["ai_tool"]["name"]).to eq(ai_tool.name)
end end

View File

@ -2,6 +2,7 @@
RSpec.describe "Admin AI persona configuration", type: :system, js: true do RSpec.describe "Admin AI persona configuration", type: :system, js: true do
fab!(:admin) fab!(:admin)
let(:admin_header) { PageObjects::Components::AdminHeader.new }
before do before do
SiteSetting.ai_bot_enabled = true SiteSetting.ai_bot_enabled = true
@ -11,7 +12,13 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do
it "allows creation of a persona" do it "allows creation of a persona" do
visit "/admin/plugins/discourse-ai/ai-personas" visit "/admin/plugins/discourse-ai/ai-personas"
expect(admin_header).to be_visible
find(".ai-persona-list-editor__new-button").click() find(".ai-persona-list-editor__new-button").click()
expect(admin_header).to be_hidden
find(".ai-persona-editor__name").set("Test Persona") 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__description").fill_in(with: "I am a test persona")
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot") find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
@ -35,7 +42,7 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do
expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new") expect(page).not_to have_current_path("/admin/plugins/discourse-ai/ai-personas/new")
persona_id = page.current_path.split("/").last.to_i persona_id = page.current_path.split("/")[-2].to_i
persona = AiPersona.find(persona_id) persona = AiPersona.find(persona_id)
expect(persona.name).to eq("Test Persona") expect(persona.name).to eq("Test Persona")
@ -46,7 +53,7 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do
end end
it "will not allow deletion or editing of system personas" do 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}" visit "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}/edit"
expect(page).not_to have_selector(".ai-persona-editor__delete") expect(page).not_to have_selector(".ai-persona-editor__delete")
expect(find(".ai-persona-editor__system_prompt")).to be_disabled expect(find(".ai-persona-editor__system_prompt")).to be_disabled
end end
@ -54,7 +61,7 @@ RSpec.describe "Admin AI persona configuration", type: :system, js: true do
it "will enable persona right away when you click on enable but does not save side effects" do it "will enable persona right away when you click on enable but does not save side effects" do
persona = Fabricate(:ai_persona, enabled: false) persona = Fabricate(:ai_persona, enabled: false)
visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}" visit "/admin/plugins/discourse-ai/ai-personas/#{persona.id}/edit"
find(".ai-persona-editor__name").set("Test Persona 1") find(".ai-persona-editor__name").set("Test Persona 1")
PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle PageObjects::Components::DToggleSwitch.new(".ai-persona-editor__enabled").toggle

View File

@ -4,6 +4,7 @@ require "rails_helper"
describe "AI Tool Management", type: :system do describe "AI Tool Management", type: :system do
fab!(:admin) fab!(:admin)
let(:admin_header) { PageObjects::Components::AdminHeader.new }
before do before do
SiteSetting.ai_embeddings_enabled = true SiteSetting.ai_embeddings_enabled = true
@ -36,9 +37,11 @@ describe "AI Tool Management", type: :system do
it "allows admin to create a new AI tool from preset" do it "allows admin to create a new AI tool from preset" do
visit "/admin/plugins/discourse-ai/ai-tools" visit "/admin/plugins/discourse-ai/ai-tools"
expect(admin_header).to be_visible
expect(page).to have_content("Tools") expect(page).to have_content("Tools")
find(".ai-tool-list-editor__new-button").click find(".ai-tool-list-editor__new-button").click
expect(admin_header).to be_hidden
select_kit = PageObjects::Components::SelectKit.new(".ai-tool-editor__presets") select_kit = PageObjects::Components::SelectKit.new(".ai-tool-editor__presets")
select_kit.expand select_kit.expand
@ -58,7 +61,7 @@ describe "AI Tool Management", type: :system do
expect(page).to have_content("Tool saved") expect(page).to have_content("Tool saved")
last_tool = AiTool.order("id desc").limit(1).first last_tool = AiTool.order("id desc").limit(1).first
visit "/admin/plugins/discourse-ai/ai-tools/#{last_tool.id}" visit "/admin/plugins/discourse-ai/ai-tools/#{last_tool.id}/edit"
ensure_can_run_test ensure_can_run_test

View File

@ -2,6 +2,7 @@
RSpec.describe "Managing LLM configurations", type: :system, js: true do RSpec.describe "Managing LLM configurations", type: :system, js: true do
fab!(:admin) fab!(:admin)
let(:admin_header) { PageObjects::Components::AdminHeader.new }
before do before do
SiteSetting.ai_bot_enabled = true SiteSetting.ai_bot_enabled = true
@ -36,8 +37,10 @@ RSpec.describe "Managing LLM configurations", type: :system, js: true do
it "manually configures an LLM" do it "manually configures an LLM" do
visit "/admin/plugins/discourse-ai/ai-llms" visit "/admin/plugins/discourse-ai/ai-llms"
expect(admin_header).to be_visible
find("[data-llm-id='none'] button").click() find("[data-llm-id='none'] button").click()
expect(admin_header).to be_hidden
find("input.ai-llm-editor__display-name").fill_in(with: "Self-hosted LLM") find("input.ai-llm-editor__display-name").fill_in(with: "Self-hosted LLM")
find("input.ai-llm-editor__name").fill_in(with: "llava-hf/llava-v1.6-mistral-7b-hf") find("input.ai-llm-editor__name").fill_in(with: "llava-hf/llava-v1.6-mistral-7b-hf")