mirror of
https://github.com/discourse/discourse.git
synced 2026-08-03 09:53:24 -05:00
UX: Move AI bot PM to navigation menu (#35189)
The header space is becoming very crowded with translations, chat, and AI bot all enabled. This commit makes the new default for AI conversations a link in the community section in the sidebar instead of a header button. <img width="1412" height="246" alt="CleanShot 2025-10-04 at 15 44 05@2x" src="https://github.com/user-attachments/assets/45c48607-bbaa-4993-9e92-bb8db2d7f45a" /> <img width="478" height="650" alt="CleanShot 2025-10-04 at 15 44 32@2x" src="https://github.com/user-attachments/assets/67af3afa-6a46-4c79-8aa9-fc789a086056" /> There is also a new back-to-forum button added in, conform with other custom-sidebar pages such as /admin and /docs. <img width="1966" height="1004" alt="CleanShot 2025-10-04 at 15 45 04@2x" src="https://github.com/user-attachments/assets/5b167aba-9305-476f-a449-ec4a186bc6f7" /> --------- Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
This commit is contained in:
+9
@@ -2,6 +2,7 @@ import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import BackToForum from "discourse/components/sidebar/back-to-forum";
|
||||
import { AI_CONVERSATIONS_PANEL } from "../services/ai-conversations-sidebar-manager";
|
||||
|
||||
const TEXTAREA_ID = "ai-bot-conversations-input";
|
||||
@@ -10,11 +11,16 @@ export default class AiBotSidebarNewConversation extends Component {
|
||||
@service appEvents;
|
||||
@service router;
|
||||
@service sidebarState;
|
||||
@service siteSettings;
|
||||
|
||||
get shouldRender() {
|
||||
return this.sidebarState.isCurrentPanel(AI_CONVERSATIONS_PANEL);
|
||||
}
|
||||
|
||||
get shouldShowBackLink() {
|
||||
return !this.siteSettings.ai_bot_add_to_header;
|
||||
}
|
||||
|
||||
@action
|
||||
focusTextarea() {
|
||||
document.getElementById(TEXTAREA_ID)?.focus();
|
||||
@@ -35,6 +41,9 @@ export default class AiBotSidebarNewConversation extends Component {
|
||||
|
||||
<template>
|
||||
{{#if this.shouldRender}}
|
||||
{{#if this.shouldShowBackLink}}
|
||||
<BackToForum />
|
||||
{{/if}}
|
||||
<div class="ai-new-question-button__wrapper">
|
||||
<DButton
|
||||
@label="discourse_ai.ai_bot.conversations.new"
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default {
|
||||
name: "add-ai-bot-to-community-section",
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup("service:site-settings");
|
||||
const currentUser = container.lookup("service:current-user");
|
||||
|
||||
if (
|
||||
!currentUser ||
|
||||
!siteSettings.ai_bot_add_to_community_section ||
|
||||
!currentUser.ai_enabled_chat_bots?.length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const availableBots = currentUser.ai_enabled_chat_bots.filter(
|
||||
(bot) => !bot.is_persona || bot.has_default_llm
|
||||
);
|
||||
|
||||
if (availableBots.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
withPluginApi((api) => {
|
||||
api.addCommunitySectionLink((baseSectionLink) => {
|
||||
return class AiBotSectionLink extends baseSectionLink {
|
||||
name = "ai-bot";
|
||||
route = "discourse-ai-bot-conversations";
|
||||
text = i18n("discourse_ai.ai_bot.shortcut_link");
|
||||
title = i18n("discourse_ai.ai_bot.shortcut_title");
|
||||
defaultPrefixValue = "robot";
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -13,7 +13,7 @@ function ensureBotMap() {
|
||||
"service:current-user"
|
||||
);
|
||||
enabledChatBotMap = {};
|
||||
currentUser.ai_enabled_chat_bots.forEach((bot) => {
|
||||
currentUser?.ai_enabled_chat_bots?.forEach((bot) => {
|
||||
enabledChatBotMap[bot.id] = bot;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,12 +6,21 @@
|
||||
}
|
||||
|
||||
body.has-ai-conversations-sidebar {
|
||||
.sidebar-sections__back-to-forum {
|
||||
order: -1;
|
||||
padding: var(--space-6) var(--space-4) var(--space-3);
|
||||
|
||||
+ .ai-new-question-button__wrapper {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-new-question-button {
|
||||
width: 100%;
|
||||
|
||||
&__wrapper {
|
||||
background: var(--secondary);
|
||||
margin: 1.8em 1em 0;
|
||||
margin: var(--space-6) var(--space-4) 0;
|
||||
|
||||
.mobile-view & {
|
||||
padding: 1em;
|
||||
|
||||
@@ -909,6 +909,7 @@ en:
|
||||
cancel_streaming: "Stop reply"
|
||||
default_pm_prefix: "[Untitled AI bot PM]"
|
||||
shortcut_title: "Start a PM with an AI bot"
|
||||
shortcut_link: "AI bot"
|
||||
exit: "exit AI bot"
|
||||
share: "Copy AI conversation"
|
||||
conversation_shared: "Conversation copied"
|
||||
|
||||
@@ -121,7 +121,8 @@ en:
|
||||
ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups."
|
||||
ai_bot_debugging_allowed_groups: "Allow these groups to see a debug button on posts which displays the raw AI request and response"
|
||||
ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI personal messages with the public via a unique publicly available link. Note: if your site requires login, shares will also require login."
|
||||
ai_bot_add_to_header: "Display a button in the header to start a PM with a AI Bot"
|
||||
ai_bot_add_to_header: "Display a button in the header to start a PM with an AI Bot"
|
||||
ai_bot_add_to_community_section: "Display a link in the sidebar community section to start a PM with an AI Bot"
|
||||
ai_bot_github_access_token: "GitHub access token for use with GitHub AI tools (required for search support)"
|
||||
|
||||
ai_discover_enabled: "Enable the discovery search feature"
|
||||
|
||||
@@ -389,6 +389,10 @@ discourse_ai:
|
||||
refresh: true
|
||||
area: "ai-features/bot"
|
||||
ai_bot_add_to_header:
|
||||
default: false
|
||||
client: true
|
||||
area: "ai-features/bot"
|
||||
ai_bot_add_to_community_section:
|
||||
default: true
|
||||
client: true
|
||||
area: "ai-features/bot"
|
||||
|
||||
@@ -9,6 +9,7 @@ RSpec.describe "AI chat channel summarization", type: :system do
|
||||
before do
|
||||
enable_current_plugin
|
||||
SiteSetting.ai_bot_enabled = true
|
||||
SiteSetting.ai_bot_add_to_header = true
|
||||
toggle_enabled_bots(bots: [gpt_4, gpt_3_5_turbo])
|
||||
SiteSetting.ai_bot_allowed_groups = group.id.to_s
|
||||
sign_in(user)
|
||||
|
||||
@@ -137,293 +137,302 @@ RSpec.describe "AI Bot - Homepage", type: :system do
|
||||
end
|
||||
end
|
||||
|
||||
it "allows uploading files to a new conversation" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
context "when `ai_bot_enable_dedicated_ux` is enabled" do
|
||||
before { SiteSetting.ai_bot_add_to_header = true }
|
||||
|
||||
file_path_1 = file_from_fixtures("logo.png", "images").path
|
||||
file_path_2 = file_from_fixtures("logo.jpg", "images").path
|
||||
attach_file("ai-bot-file-uploader", [file_path_1, file_path_2], make_visible: true)
|
||||
|
||||
expect(page).to have_css(".ai-bot-upload", count: 2)
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Here are two image attachments")
|
||||
|
||||
expect(page).to have_no_css(".ai-bot-upload--in-progress")
|
||||
|
||||
responses = ["hello user", "topic title"]
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(responses) do
|
||||
ai_pm_homepage.submit
|
||||
expect(topic_page).to have_content("Here are two image attachments")
|
||||
expect(page).to have_css(".cooked img", count: 2)
|
||||
end
|
||||
|
||||
find(".ai-new-question-button").click
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(page).to have_no_css(".ai-bot-upload")
|
||||
end
|
||||
|
||||
it "shows an error when trying to submit while uploads are in progress" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
|
||||
file_path_1 = file_from_fixtures("logo.png", "images").path
|
||||
file_path_2 = file_from_fixtures("logo.jpg", "images").path
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Some message to send to AI with uploads")
|
||||
|
||||
cdp.with_slow_upload do
|
||||
attach_file("ai-bot-file-uploader", [file_path_1, file_path_2], make_visible: true)
|
||||
expect(page).to have_css(".ai-bot-upload--in-progress", count: 2)
|
||||
|
||||
ai_pm_homepage.submit
|
||||
expect(page).to have_content(
|
||||
I18n.t("js.discourse_ai.ai_bot.conversations.uploads_in_progress"),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it "allows removing an upload before submission" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
|
||||
file_path = file_from_fixtures("logo.png", "images").path
|
||||
attach_file("ai-bot-file-uploader", file_path, make_visible: true)
|
||||
expect(page).to have_css(".ai-bot-upload", count: 1)
|
||||
|
||||
find(".ai-bot-upload__remove").click
|
||||
|
||||
expect(page).to have_no_css(".ai-bot-upload")
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Message without attachments")
|
||||
|
||||
responses = ["hello user", "topic title"]
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(responses) do
|
||||
ai_pm_homepage.submit
|
||||
expect(topic_page).to have_content("Message without attachments")
|
||||
expect(page).to have_no_css(".cooked img")
|
||||
end
|
||||
end
|
||||
|
||||
it "renders landing page on bot click" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to be_visible
|
||||
end
|
||||
|
||||
it "displays error when message is too short" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "a")
|
||||
ai_pm_homepage.submit
|
||||
expect(ai_pm_homepage).to have_too_short_dialog
|
||||
dialog.click_yes
|
||||
expect(composer).to be_closed
|
||||
end
|
||||
|
||||
it "hides default content in the sidebar" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_no_tags_section
|
||||
expect(sidebar).to have_no_section("categories")
|
||||
expect(sidebar).to have_no_section("chat-dms")
|
||||
expect(sidebar).to have_no_section("chat-channels")
|
||||
expect(sidebar).to have_no_section("user-threads")
|
||||
end
|
||||
|
||||
it "shows the bot conversation in the sidebar" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Today")
|
||||
expect(sidebar).to have_section_link(pm.title)
|
||||
end
|
||||
|
||||
it "allows navigating to a specific LLM and persona" do
|
||||
# url encode name
|
||||
persona_name = CGI.escape(persona.name)
|
||||
llm_name = CGI.escape(claude_2_dup.display_name)
|
||||
visit "/discourse-ai/ai-bot/conversations?persona=#{persona_name}&llm=#{llm_name}"
|
||||
|
||||
ai_pm_homepage.persona_selector.expand # not needed, but helps to see what the list has
|
||||
expect(ai_pm_homepage.persona_selector).to have_selected_name(persona.name)
|
||||
expect(ai_pm_homepage.llm_selector).to have_selected_name(claude_2_dup.display_name)
|
||||
end
|
||||
|
||||
it "removes persona from selector when allow_personal_messages is disabled" do
|
||||
begin
|
||||
persona.update!(allow_personal_messages: false)
|
||||
it "allows uploading files to a new conversation" do
|
||||
ai_pm_homepage.visit
|
||||
ai_pm_homepage.persona_selector.expand
|
||||
expect(ai_pm_homepage.persona_selector).to have_no_option_name(persona.name)
|
||||
ensure
|
||||
persona.update!(allow_personal_messages: true)
|
||||
end
|
||||
end
|
||||
|
||||
it "includes persona in selector when allow_personal_messages is enabled" do
|
||||
# default is true
|
||||
ai_pm_homepage.visit
|
||||
ai_pm_homepage.persona_selector.expand
|
||||
expect(ai_pm_homepage.persona_selector).to have_option_name(persona.name)
|
||||
end
|
||||
|
||||
it "shows empty state when no PMs exist" do
|
||||
pm.destroy!
|
||||
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(page).to have_css(".sidebar-section .ai-bot-sidebar-empty-state", visible: true)
|
||||
end
|
||||
|
||||
it "doesn't show empty state when a PM exists" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(page).to have_no_css(".sidebar-section .ai-bot-sidebar-empty-state")
|
||||
end
|
||||
|
||||
it "displays last_7_days label in the sidebar" do
|
||||
pm.update!(last_posted_at: 5.days.ago)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Last 7 days")
|
||||
end
|
||||
|
||||
it "displays last_30_days label in the sidebar" do
|
||||
pm.update!(last_posted_at: 28.days.ago)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Last 30 days")
|
||||
end
|
||||
|
||||
it "displays month and year label in the sidebar for older conversations" do
|
||||
pm.update!(last_posted_at: "2024-04-10 15:39:11.406192000 +00:00")
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("2024-3")
|
||||
end
|
||||
|
||||
it "navigates to the bot conversation when clicked" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
ai_pm_homepage.click_fist_sidebar_conversation
|
||||
expect(topic_page).to have_topic_title(pm.title)
|
||||
end
|
||||
|
||||
it "displays the shuffle icon when on homepage or bot PM" do
|
||||
visit "/"
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
header.click_bot_button
|
||||
|
||||
expect(header).to have_icon_in_bot_button(icon: "shuffle")
|
||||
|
||||
# Go to a PM and assert that the icon is still shuffle
|
||||
ai_pm_homepage.click_fist_sidebar_conversation
|
||||
expect(header).to have_icon_in_bot_button(icon: "shuffle")
|
||||
|
||||
# Go back home and assert that the icon is now robot again
|
||||
header.click_bot_button
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
end
|
||||
|
||||
it "displays 'new question' button on homepage and topic page" do
|
||||
topic_page.visit_topic(pm)
|
||||
expect(ai_pm_homepage).to have_new_question_button
|
||||
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_new_question_button
|
||||
end
|
||||
|
||||
it "redirect to the homepage when 'new question' is clicked" do
|
||||
topic_page.visit_topic(pm)
|
||||
expect(sidebar).to be_visible
|
||||
ai_pm_homepage.click_new_question_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
end
|
||||
|
||||
it "can send a new message to the bot" do
|
||||
topic_page.visit_topic(pm)
|
||||
topic_page.click_reply_button
|
||||
expect(composer).to be_opened
|
||||
|
||||
composer.fill_in(with: "Hello bot replying to you")
|
||||
composer.submit
|
||||
expect(page).to have_content("Hello bot replying to you")
|
||||
end
|
||||
|
||||
it "does not render custom sidebar on non-authored bot pms" do
|
||||
# Include user_2 in the PM by creating a new post and topic_allowed_user association
|
||||
Fabricate(:post, topic: pm, user: user_2, post_number: 4)
|
||||
Fabricate(:topic_allowed_user, topic: pm, user: user_2)
|
||||
sign_in(user_2)
|
||||
|
||||
topic_page.visit_topic(pm)
|
||||
|
||||
expect(sidebar).to be_visible
|
||||
expect(sidebar).to have_no_section("ai-conversations-history")
|
||||
expect(sidebar).to have_no_css("button.ai-new-question-button")
|
||||
end
|
||||
|
||||
it "does not include non-authored bot pms in sidebar" do
|
||||
# Include user_2 in the PM by creating a new post and topic_allowed_user association
|
||||
Fabricate(:post, topic: pm, user: user_2, post_number: 4)
|
||||
Fabricate(:topic_allowed_user, topic: pm, user: user_2)
|
||||
sign_in(user_2)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_no_section_link(pm.title)
|
||||
end
|
||||
|
||||
it "Allows choosing persona and LLM" do
|
||||
ai_pm_homepage.visit
|
||||
|
||||
ai_pm_homepage.llm_selector.expand
|
||||
ai_pm_homepage.llm_selector.select_row_by_name(claude_2_dup.display_name)
|
||||
ai_pm_homepage.llm_selector.collapse
|
||||
|
||||
# confirm memory works for llm selection
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage.llm_selector).to have_selected_name(claude_2_dup.display_name)
|
||||
end
|
||||
|
||||
it "does not render back to forum link" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_no_sidebar_back_link
|
||||
end
|
||||
|
||||
context "with hamburger menu" do
|
||||
before { SiteSetting.navigation_menu = "header dropdown" }
|
||||
it "keeps robot icon in the header and doesn't display sidebar back link" do
|
||||
visit "/"
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
expect(ai_pm_homepage).to have_no_sidebar_back_link
|
||||
|
||||
file_path_1 = file_from_fixtures("logo.png", "images").path
|
||||
file_path_2 = file_from_fixtures("logo.jpg", "images").path
|
||||
attach_file("ai-bot-file-uploader", [file_path_1, file_path_2], make_visible: true)
|
||||
|
||||
expect(page).to have_css(".ai-bot-upload", count: 2)
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Here are two image attachments")
|
||||
|
||||
expect(page).to have_no_css(".ai-bot-upload--in-progress")
|
||||
|
||||
responses = ["hello user", "topic title"]
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(responses) do
|
||||
ai_pm_homepage.submit
|
||||
expect(topic_page).to have_content("Here are two image attachments")
|
||||
expect(page).to have_css(".cooked img", count: 2)
|
||||
end
|
||||
|
||||
find(".ai-new-question-button").click
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(page).to have_no_css(".ai-bot-upload")
|
||||
end
|
||||
|
||||
it "still renders the sidebar" do
|
||||
it "shows an error when trying to submit while uploads are in progress" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
|
||||
file_path_1 = file_from_fixtures("logo.png", "images").path
|
||||
file_path_2 = file_from_fixtures("logo.jpg", "images").path
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Some message to send to AI with uploads")
|
||||
|
||||
cdp.with_slow_upload do
|
||||
attach_file("ai-bot-file-uploader", [file_path_1, file_path_2], make_visible: true)
|
||||
expect(page).to have_css(".ai-bot-upload--in-progress", count: 2)
|
||||
|
||||
ai_pm_homepage.submit
|
||||
expect(page).to have_content(
|
||||
I18n.t("js.discourse_ai.ai_bot.conversations.uploads_in_progress"),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it "allows removing an upload before submission" do
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
|
||||
file_path = file_from_fixtures("logo.png", "images").path
|
||||
attach_file("ai-bot-file-uploader", file_path, make_visible: true)
|
||||
expect(page).to have_css(".ai-bot-upload", count: 1)
|
||||
|
||||
find(".ai-bot-upload__remove").click
|
||||
|
||||
expect(page).to have_no_css(".ai-bot-upload")
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "Message without attachments")
|
||||
|
||||
responses = ["hello user", "topic title"]
|
||||
DiscourseAi::Completions::Llm.with_prepared_responses(responses) do
|
||||
ai_pm_homepage.submit
|
||||
expect(topic_page).to have_content("Message without attachments")
|
||||
expect(page).to have_no_css(".cooked img")
|
||||
end
|
||||
end
|
||||
|
||||
it "renders landing page on bot click" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to be_visible
|
||||
expect(header_dropdown).to be_visible
|
||||
end
|
||||
|
||||
it "displays error when message is too short" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
ai_pm_homepage.input.fill_in(with: "a")
|
||||
ai_pm_homepage.submit
|
||||
expect(ai_pm_homepage).to have_too_short_dialog
|
||||
dialog.click_yes
|
||||
expect(composer).to be_closed
|
||||
end
|
||||
|
||||
it "hides default content in the sidebar" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_no_tags_section
|
||||
expect(sidebar).to have_no_section("categories")
|
||||
expect(sidebar).to have_no_section("chat-dms")
|
||||
expect(sidebar).to have_no_section("chat-channels")
|
||||
expect(sidebar).to have_no_section("user-threads")
|
||||
end
|
||||
|
||||
it "shows the bot conversation in the sidebar" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Today")
|
||||
expect(sidebar).to have_section_link(pm.title)
|
||||
end
|
||||
|
||||
it "allows navigating to a specific LLM and persona" do
|
||||
# url encode name
|
||||
persona_name = CGI.escape(persona.name)
|
||||
llm_name = CGI.escape(claude_2_dup.display_name)
|
||||
visit "/discourse-ai/ai-bot/conversations?persona=#{persona_name}&llm=#{llm_name}"
|
||||
|
||||
ai_pm_homepage.persona_selector.expand # not needed, but helps to see what the list has
|
||||
expect(ai_pm_homepage.persona_selector).to have_selected_name(persona.name)
|
||||
expect(ai_pm_homepage.llm_selector).to have_selected_name(claude_2_dup.display_name)
|
||||
end
|
||||
|
||||
it "removes persona from selector when allow_personal_messages is disabled" do
|
||||
begin
|
||||
persona.update!(allow_personal_messages: false)
|
||||
ai_pm_homepage.visit
|
||||
ai_pm_homepage.persona_selector.expand
|
||||
expect(ai_pm_homepage.persona_selector).to have_no_option_name(persona.name)
|
||||
ensure
|
||||
persona.update!(allow_personal_messages: true)
|
||||
end
|
||||
end
|
||||
|
||||
it "includes persona in selector when allow_personal_messages is enabled" do
|
||||
# default is true
|
||||
ai_pm_homepage.visit
|
||||
ai_pm_homepage.persona_selector.expand
|
||||
expect(ai_pm_homepage.persona_selector).to have_option_name(persona.name)
|
||||
end
|
||||
|
||||
it "shows empty state when no PMs exist" do
|
||||
pm.destroy!
|
||||
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(page).to have_css(".sidebar-section .ai-bot-sidebar-empty-state", visible: true)
|
||||
end
|
||||
|
||||
it "doesn't show empty state when a PM exists" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(page).to have_no_css(".sidebar-section .ai-bot-sidebar-empty-state")
|
||||
end
|
||||
|
||||
it "displays last_7_days label in the sidebar" do
|
||||
pm.update!(last_posted_at: 5.days.ago)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Last 7 days")
|
||||
end
|
||||
|
||||
it "displays last_30_days label in the sidebar" do
|
||||
pm.update!(last_posted_at: 28.days.ago)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("Last 30 days")
|
||||
end
|
||||
|
||||
it "displays month and year label in the sidebar for older conversations" do
|
||||
pm.update!(last_posted_at: "2024-04-10 15:39:11.406192000 +00:00")
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_section("2024-3")
|
||||
end
|
||||
|
||||
it "navigates to the bot conversation when clicked" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
ai_pm_homepage.click_fist_sidebar_conversation
|
||||
expect(topic_page).to have_topic_title(pm.title)
|
||||
end
|
||||
|
||||
it "displays the shuffle icon when on homepage or bot PM" do
|
||||
visit "/"
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
header.click_bot_button
|
||||
|
||||
expect(header).to have_icon_in_bot_button(icon: "shuffle")
|
||||
|
||||
# Go to a PM and assert that the icon is still shuffle
|
||||
ai_pm_homepage.click_fist_sidebar_conversation
|
||||
expect(header).to have_icon_in_bot_button(icon: "shuffle")
|
||||
|
||||
# Go back home and assert that the icon is now robot again
|
||||
header.click_bot_button
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
end
|
||||
|
||||
it "displays 'new question' button on homepage and topic page" do
|
||||
topic_page.visit_topic(pm)
|
||||
expect(ai_pm_homepage).to have_new_question_button
|
||||
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_new_question_button
|
||||
end
|
||||
|
||||
it "redirect to the homepage when 'new question' is clicked" do
|
||||
topic_page.visit_topic(pm)
|
||||
expect(sidebar).to be_visible
|
||||
ai_pm_homepage.click_new_question_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
end
|
||||
|
||||
it "can send a new message to the bot" do
|
||||
topic_page.visit_topic(pm)
|
||||
topic_page.click_reply_button
|
||||
expect(composer).to be_opened
|
||||
|
||||
composer.fill_in(with: "Hello bot replying to you")
|
||||
composer.submit
|
||||
expect(page).to have_content("Hello bot replying to you")
|
||||
end
|
||||
|
||||
it "does not render custom sidebar on non-authored bot pms" do
|
||||
# Include user_2 in the PM by creating a new post and topic_allowed_user association
|
||||
Fabricate(:post, topic: pm, user: user_2, post_number: 4)
|
||||
Fabricate(:topic_allowed_user, topic: pm, user: user_2)
|
||||
sign_in(user_2)
|
||||
|
||||
topic_page.visit_topic(pm)
|
||||
|
||||
expect(sidebar).to be_visible
|
||||
expect(sidebar).to have_no_section("ai-conversations-history")
|
||||
expect(sidebar).to have_no_css("button.ai-new-question-button")
|
||||
end
|
||||
|
||||
it "does not include non-authored bot pms in sidebar" do
|
||||
# Include user_2 in the PM by creating a new post and topic_allowed_user association
|
||||
Fabricate(:post, topic: pm, user: user_2, post_number: 4)
|
||||
Fabricate(:topic_allowed_user, topic: pm, user: user_2)
|
||||
sign_in(user_2)
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to have_no_section_link(pm.title)
|
||||
end
|
||||
|
||||
it "Allows choosing persona and LLM" do
|
||||
ai_pm_homepage.visit
|
||||
|
||||
ai_pm_homepage.llm_selector.expand
|
||||
ai_pm_homepage.llm_selector.select_row_by_name(claude_2_dup.display_name)
|
||||
ai_pm_homepage.llm_selector.collapse
|
||||
|
||||
# confirm memory works for llm selection
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage.llm_selector).to have_selected_name(claude_2_dup.display_name)
|
||||
end
|
||||
|
||||
it "renders back to forum link when header button is disabled" do
|
||||
SiteSetting.ai_bot_add_to_header = false
|
||||
ai_pm_homepage.visit
|
||||
expect(ai_pm_homepage).to have_sidebar_back_link
|
||||
end
|
||||
|
||||
context "with hamburger menu" do
|
||||
before do
|
||||
SiteSetting.navigation_menu = "header dropdown"
|
||||
SiteSetting.ai_bot_add_to_header = true
|
||||
end
|
||||
|
||||
it "keeps robot icon in the header and doesn't display sidebar back link" do
|
||||
visit "/"
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(header).to have_icon_in_bot_button(icon: "robot")
|
||||
expect(ai_pm_homepage).to have_no_sidebar_back_link
|
||||
end
|
||||
|
||||
it "still renders the sidebar" do
|
||||
visit "/"
|
||||
header.click_bot_button
|
||||
expect(ai_pm_homepage).to have_homepage
|
||||
expect(sidebar).to be_visible
|
||||
expect(header_dropdown).to be_visible
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ RSpec.describe "AI personas", type: :system do
|
||||
before do
|
||||
enable_current_plugin
|
||||
SiteSetting.ai_bot_enabled = true
|
||||
SiteSetting.ai_bot_add_to_header = true
|
||||
toggle_enabled_bots(bots: [gpt_4])
|
||||
sign_in(admin)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
acceptance("AI Bot - Sidebar community link", function (needs) {
|
||||
needs.user({
|
||||
ai_enabled_chat_bots: [
|
||||
{
|
||||
id: 1,
|
||||
model_name: "gpt-4",
|
||||
is_persona: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
needs.settings({
|
||||
discourse_ai_enabled: true,
|
||||
ai_bot_enabled: true,
|
||||
ai_bot_add_to_community_section: true,
|
||||
});
|
||||
|
||||
test("displays AI bot link in community section when enabled", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.exists("AI bot link is displayed in the sidebar");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot'] .d-icon-robot")
|
||||
.exists("AI bot link has robot icon");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.hasText("AI bot", "AI bot link has correct text");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("AI Bot - Sidebar community link - disabled", function (needs) {
|
||||
needs.user({
|
||||
ai_enabled_chat_bots: [
|
||||
{
|
||||
id: 1,
|
||||
model_name: "gpt-4",
|
||||
is_persona: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
needs.settings({
|
||||
discourse_ai_enabled: true,
|
||||
ai_bot_enabled: true,
|
||||
ai_bot_add_to_community_section: false,
|
||||
});
|
||||
|
||||
test("does not display AI bot link when setting is disabled", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.doesNotExist("AI bot link is not displayed when setting is disabled");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("AI Bot - Sidebar community link - no bots", function (needs) {
|
||||
needs.user({
|
||||
ai_enabled_chat_bots: [],
|
||||
});
|
||||
|
||||
needs.settings({
|
||||
discourse_ai_enabled: true,
|
||||
ai_bot_enabled: true,
|
||||
ai_bot_add_to_community_section: true,
|
||||
});
|
||||
|
||||
test("does not display AI bot link when no bots are available", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.doesNotExist("AI bot link is not displayed when no bots are available");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance(
|
||||
"AI Bot - Sidebar community link - persona without default LLM",
|
||||
function (needs) {
|
||||
needs.user({
|
||||
ai_enabled_chat_bots: [
|
||||
{
|
||||
id: 1,
|
||||
model_name: "custom-persona",
|
||||
is_persona: true,
|
||||
has_default_llm: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
needs.settings({
|
||||
discourse_ai_enabled: true,
|
||||
ai_bot_enabled: true,
|
||||
ai_bot_add_to_community_section: true,
|
||||
});
|
||||
|
||||
test("does not display AI bot link when persona has no default LLM", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.doesNotExist(
|
||||
"AI bot link is not displayed when persona lacks default LLM"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
acceptance(
|
||||
"AI Bot - Sidebar community link - persona with default LLM",
|
||||
function (needs) {
|
||||
needs.user({
|
||||
ai_enabled_chat_bots: [
|
||||
{
|
||||
id: 1,
|
||||
model_name: "custom-persona",
|
||||
is_persona: true,
|
||||
has_default_llm: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
needs.settings({
|
||||
discourse_ai_enabled: true,
|
||||
ai_bot_enabled: true,
|
||||
ai_bot_add_to_community_section: true,
|
||||
});
|
||||
|
||||
test("displays AI bot link when persona has default LLM", async function (assert) {
|
||||
await visit("/");
|
||||
|
||||
assert
|
||||
.dom(".sidebar-section-link[data-link-name='ai-bot']")
|
||||
.exists("AI bot link is displayed when persona has default LLM");
|
||||
});
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user