REVERT: "FEATURE: Experimental Private Message Bot Homepage (#1159)" (#1272)

This reverts commit 5fec8fe79e.
This commit is contained in:
Mark VanLandingham 2025-04-21 16:42:05 -05:00 committed by GitHub
parent 5fec8fe79e
commit 244ec9d61e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 16 additions and 1149 deletions

View File

@ -1,35 +0,0 @@
# frozen_string_literal: true
module DiscourseAi
module AiBot
class ConversationsController < ::ApplicationController
requires_plugin ::DiscourseAi::PLUGIN_NAME
requires_login
def index
page = params[:page].to_i
per_page = params[:per_page]&.to_i || 40
bot_user_ids = EntryPoint.all_bot_ids
base_query =
Topic
.private_messages_for_user(current_user)
.joins(:topic_users)
.where(topic_users: { user_id: bot_user_ids })
.distinct
total = base_query.count
pms = base_query.order(last_posted_at: :desc).offset(page * per_page).limit(per_page)
render json: {
conversations: serialize_data(pms, BasicTopicSerializer),
meta: {
total: total,
page: page,
per_page: per_page,
has_more: total > (page + 1) * per_page,
},
}
end
end
end
end

View File

@ -9,7 +9,6 @@ export default class AiBotHeaderIcon extends Component {
@service currentUser;
@service siteSettings;
@service composer;
@service router;
get bots() {
const availableBots = this.currentUser.ai_enabled_chat_bots
@ -25,9 +24,6 @@ export default class AiBotHeaderIcon extends Component {
@action
compose() {
if (this.siteSettings.ai_enable_experimental_bot_ux) {
return this.router.transitionTo("discourse-ai-bot-conversations");
}
composeAiBotMessage(this.bots[0], this.composer);
}

View File

@ -1,27 +0,0 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { AI_CONVERSATIONS_PANEL } from "../services/ai-conversations-sidebar-manager";
export default class AiBotSidebarNewConversation extends Component {
@service router;
@service sidebarState;
get shouldRender() {
return (
this.router.currentRouteName !== "discourse-ai-bot-conversations" &&
this.sidebarState.isCurrentPanel(AI_CONVERSATIONS_PANEL)
);
}
<template>
{{#if this.shouldRender}}
<DButton
@route="/discourse-ai/ai-bot/conversations"
@label="discourse_ai.ai_bot.conversations.new"
@icon="plus"
class="ai-new-question-button btn-default"
/>
{{/if}}
</template>
}

View File

@ -1,70 +0,0 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { tracked } from "@ember-compat/tracked-built-ins";
export default class DiscourseAiBotConversations extends Controller {
@service aiBotConversationsHiddenSubmit;
@service currentUser;
@tracked selectedPersona = this.personaOptions[0].username;
textarea = null;
init() {
super.init(...arguments);
this.selectedPersonaChanged(this.selectedPersona);
}
get personaOptions() {
if (this.currentUser.ai_enabled_personas) {
return this.currentUser.ai_enabled_personas
.filter((persona) => persona.username)
.map((persona) => {
return {
id: persona.id,
username: persona.username,
name: persona.name,
description: persona.description,
};
});
}
}
get displayPersonaSelector() {
return this.personaOptions.length > 1;
}
get filterable() {
return this.personaOptions.length > 4;
}
@action
selectedPersonaChanged(username) {
this.selectedPersona = username;
this.aiBotConversationsHiddenSubmit.personaUsername = username;
}
@action
updateInputValue(event) {
this._autoExpandTextarea();
this.aiBotConversationsHiddenSubmit.inputValue = event.target.value;
}
@action
handleKeyDown(event) {
if (event.key === "Enter" && !event.shiftKey) {
this.aiBotConversationsHiddenSubmit.submitToBot();
}
}
@action
setTextArea(element) {
this.textarea = element;
}
_autoExpandTextarea() {
this.textarea.style.height = "auto";
this.textarea.style.height = this.textarea.scrollHeight + "px";
}
}

View File

@ -1,5 +0,0 @@
export default function () {
this.route("discourse-ai-bot-conversations", {
path: "/discourse-ai/ai-bot/conversations",
});
}

View File

@ -23,43 +23,24 @@ export function showShareConversationModal(modal, topicId) {
.catch(popupAjaxError);
}
export async function composeAiBotMessage(
targetBot,
composer,
options = {
skipFocus: false,
topicBody: "",
personaUsername: null,
}
) {
export function composeAiBotMessage(targetBot, composer) {
const currentUser = composer.currentUser;
const draftKey = "new_private_message_ai_" + new Date().getTime();
let botUsername;
if (targetBot) {
botUsername = currentUser.ai_enabled_chat_bots.find(
(bot) => bot.model_name === targetBot
)?.username;
} else if (options.personaUsername) {
botUsername = options.personaUsername;
} else {
botUsername = currentUser.ai_enabled_chat_bots[0].username;
}
let botUsername = currentUser.ai_enabled_chat_bots.find(
(bot) => bot.model_name === targetBot
).username;
const data = {
action: Composer.PRIVATE_MESSAGE,
recipients: botUsername,
topicTitle: i18n("discourse_ai.ai_bot.default_pm_prefix"),
archetypeId: "private_message",
draftKey,
hasGroups: false,
warningsDisabled: true,
};
if (options.skipFocus) {
data.topicBody = options.topicBody;
await composer.open(data);
} else {
composer.focusComposer({ fallbackToNewTopic: true, openOpts: data });
}
composer.focusComposer({
fallbackToNewTopic: true,
openOpts: {
action: Composer.PRIVATE_MESSAGE,
recipients: botUsername,
topicTitle: i18n("discourse_ai.ai_bot.default_pm_prefix"),
archetypeId: "private_message",
draftKey,
hasGroups: false,
warningsDisabled: true,
},
});
}

View File

@ -1,62 +0,0 @@
import { action } from "@ember/object";
import { next } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { i18n } from "discourse-i18n";
import { composeAiBotMessage } from "../lib/ai-bot-helper";
export default class AiBotConversationsHiddenSubmit extends Service {
@service composer;
@service aiConversationsSidebarManager;
@service dialog;
personaUsername;
inputValue = "";
@action
focusInput() {
this.composer.destroyDraft();
this.composer.close();
next(() => {
document.getElementById("custom-homepage-input").focus();
});
}
@action
async submitToBot() {
this.composer.destroyDraft();
this.composer.close();
if (this.inputValue.length < 10) {
return this.dialog.alert({
message: i18n(
"discourse_ai.ai_bot.conversations.min_input_length_message"
),
didConfirm: () => this.focusInput(),
didCancel: () => this.focusInput(),
});
}
// we are intentionally passing null as the targetBot to allow for the
// function to select the first available bot. This will be refactored in the
// future to allow for selecting a specific bot.
await composeAiBotMessage(null, this.composer, {
skipFocus: true,
topicBody: this.inputValue,
personaUsername: this.personaUsername,
});
try {
await this.composer.save();
this.aiConversationsSidebarManager.newTopicForceSidebar = true;
if (this.inputValue.length > 10) {
// prevents submitting same message again when returning home
// but avoids deleting too-short message on submit
this.inputValue = "";
}
} catch (e) {
popupAjaxError(e);
}
}
}

View File

@ -1,40 +0,0 @@
import { tracked } from "@glimmer/tracking";
import Service, { service } from "@ember/service";
import { ADMIN_PANEL, MAIN_PANEL } from "discourse/lib/sidebar/panels";
export const AI_CONVERSATIONS_PANEL = "ai-conversations";
export default class AiConversationsSidebarManager extends Service {
@service sidebarState;
@tracked newTopicForceSidebar = false;
forceCustomSidebar() {
// Set the panel to your custom panel
this.sidebarState.setPanel(AI_CONVERSATIONS_PANEL);
// Use separated mode to ensure independence from hamburger menu
this.sidebarState.setSeparatedMode();
// Hide panel switching buttons to keep UI clean
this.sidebarState.hideSwitchPanelButtons();
this.sidebarState.isForcingSidebar = true;
document.body.classList.add("has-ai-conversations-sidebar");
return true;
}
stopForcingCustomSidebar() {
// This method is called when leaving your route
// Only restore main panel if we previously forced ours
document.body.classList.remove("has-ai-conversations-sidebar");
const isAdminSidebarActive =
this.sidebarState.currentPanel?.key === ADMIN_PANEL;
// only restore main panel if we previously forced our sidebar
// and not if we are in admin sidebar
if (this.sidebarState.isForcingSidebar && !isAdminSidebarActive) {
this.sidebarState.setPanel(MAIN_PANEL); // Return to main sidebar panel
this.sidebarState.isForcingSidebar = false;
}
}
}

View File

@ -1,51 +0,0 @@
import { hash } from "@ember/helper";
import { on } from "@ember/modifier";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import RouteTemplate from "ember-route-template";
import DButton from "discourse/components/d-button";
import { i18n } from "discourse-i18n";
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
export default RouteTemplate(
<template>
<div class="ai-bot-conversations">
{{#if @controller.displayPersonaSelector}}
<div class="ai-bot-conversations__persona-selector">
<DropdownSelectBox
class="persona-llm-selector__persona-dropdown"
@value={{@controller.selectedPersona}}
@valueProperty="username"
@content={{@controller.personaOptions}}
@options={{hash icon="robot" filterable=@controller.filterable}}
@onChange={{@controller.selectedPersonaChanged}}
/>
</div>
{{/if}}
<div class="ai-bot-conversations__content-wrapper">
<h1>{{i18n "discourse_ai.ai_bot.conversations.header"}}</h1>
<div class="ai-bot-conversations__input-wrapper">
<textarea
{{didInsert @controller.setTextArea}}
{{on "input" @controller.updateInputValue}}
{{on "keydown" @controller.handleKeyDown}}
id="ai-bot-conversations-input"
placeholder={{i18n "discourse_ai.ai_bot.conversations.placeholder"}}
minlength="10"
rows="1"
/>
<DButton
@action={{@controller.aiBotConversationsHiddenSubmit.submitToBot}}
@icon="paper-plane"
@title="discourse_ai.ai_bot.conversations.header"
class="ai-bot-button btn-primary ai-conversation-submit"
/>
</div>
<p class="ai-disclaimer">
{{i18n "discourse_ai.ai_bot.conversations.disclaimer"}}
</p>
</div>
</div>
</template>
);

View File

@ -1,256 +0,0 @@
import { tracked } from "@glimmer/tracking";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import { ajax } from "discourse/lib/ajax";
import { bind } from "discourse/lib/decorators";
import { withPluginApi } from "discourse/lib/plugin-api";
import { i18n } from "discourse-i18n";
import AiBotSidebarNewConversation from "../discourse/components/ai-bot-sidebar-new-conversation";
import { isPostFromAiBot } from "../discourse/lib/ai-bot-helper";
import { AI_CONVERSATIONS_PANEL } from "../discourse/services/ai-conversations-sidebar-manager";
export default {
name: "ai-conversations-sidebar",
initialize() {
withPluginApi((api) => {
const currentUser = api.container.lookup("service:current-user");
if (!currentUser) {
return;
}
const aiConversationsSidebarManager = api.container.lookup(
"service:ai-conversations-sidebar-manager"
);
const appEvents = api.container.lookup("service:app-events");
const messageBus = api.container.lookup("service:message-bus");
api.addSidebarPanel(
(BaseCustomSidebarPanel) =>
class AiConversationsSidebarPanel extends BaseCustomSidebarPanel {
key = AI_CONVERSATIONS_PANEL;
hidden = true;
displayHeader = true;
expandActiveSection = true;
}
);
api.renderInOutlet("sidebar-footer-actions", AiBotSidebarNewConversation);
api.addSidebarSection(
(BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => {
const AiConversationLink = class extends BaseCustomSidebarSectionLink {
route = "topic.fromParamsNear";
constructor(topic) {
super(...arguments);
this.topic = topic;
}
get name() {
return this.topic.title;
}
get models() {
return [
this.topic.slug,
this.topic.id,
this.topic.last_read_post_number || 0,
];
}
get title() {
return this.topic.title;
}
get text() {
return this.topic.title;
}
get classNames() {
return `ai-conversation-${this.topic.id}`;
}
};
return class extends BaseCustomSidebarSection {
@tracked links = new TrackedArray();
@tracked topics = [];
@tracked hasMore = [];
page = 0;
isFetching = false;
totalTopicsCount = 0;
constructor() {
super(...arguments);
this.fetchMessages();
appEvents.on("topic:created", this, "addNewMessageToSidebar");
}
@bind
willDestroy() {
this.removeScrollListener();
appEvents.on("topic:created", this, "addNewMessageToSidebar");
}
get name() {
return "ai-conversations-history";
}
get text() {
return i18n(
"discourse_ai.ai_bot.conversations.messages_sidebar_title"
);
}
get sidebarElement() {
return document.querySelector(
".sidebar-wrapper .sidebar-sections"
);
}
addNewMessageToSidebar(topic) {
this.addNewMessage(topic);
this.watchForTitleUpdate(topic);
}
@bind
removeScrollListener() {
const sidebar = this.sidebarElement;
if (sidebar) {
sidebar.removeEventListener("scroll", this.scrollHandler);
}
}
@bind
attachScrollListener() {
const sidebar = this.sidebarElement;
if (sidebar) {
sidebar.addEventListener("scroll", this.scrollHandler);
}
}
@bind
scrollHandler() {
const sidebarElement = this.sidebarElement;
if (!sidebarElement) {
return;
}
const scrollPosition = sidebarElement.scrollTop;
const scrollHeight = sidebarElement.scrollHeight;
const clientHeight = sidebarElement.clientHeight;
// When user has scrolled to bottom with a small threshold
if (scrollHeight - scrollPosition - clientHeight < 100) {
if (this.hasMore && !this.isFetching) {
this.loadMore();
}
}
}
async fetchMessages(isLoadingMore = false) {
if (this.isFetching) {
return;
}
try {
this.isFetching = true;
const data = await ajax(
"/discourse-ai/ai-bot/conversations.json",
{
data: { page: this.page, per_page: 40 },
}
);
if (isLoadingMore) {
this.topics = [...this.topics, ...data.conversations];
} else {
this.topics = data.conversations;
}
this.totalTopicsCount = data.meta.total;
this.hasMore = data.meta.has_more;
this.isFetching = false;
this.removeScrollListener();
this.buildSidebarLinks();
this.attachScrollListener();
} catch {
this.isFetching = false;
}
}
loadMore() {
if (this.isFetching || !this.hasMore) {
return;
}
this.page = this.page + 1;
this.fetchMessages(true);
}
buildSidebarLinks() {
this.links = this.topics.map(
(topic) => new AiConversationLink(topic)
);
}
addNewMessage(newTopic) {
this.links = [new AiConversationLink(newTopic), ...this.links];
}
watchForTitleUpdate(topic) {
const channel = `/discourse-ai/ai-bot/topic/${topic.topic_id}`;
const topicId = topic.topic_id;
const callback = this.updateTopicTitle.bind(this);
messageBus.subscribe(channel, ({ title }) => {
callback(topicId, title);
messageBus.unsubscribe(channel);
});
}
updateTopicTitle(topicId, title) {
// update the topic title in the sidebar, instead of the default title
const text = document.querySelector(
`.sidebar-section-link-wrapper .ai-conversation-${topicId} .sidebar-section-link-content-text`
);
if (text) {
text.innerText = title;
}
}
};
},
AI_CONVERSATIONS_PANEL
);
const setSidebarPanel = (transition) => {
if (transition?.to?.name === "discourse-ai-bot-conversations") {
return aiConversationsSidebarManager.forceCustomSidebar();
}
const topic = api.container.lookup("controller:topic").model;
if (
topic?.archetype === "private_message" &&
topic.postStream.posts.some((post) =>
isPostFromAiBot(post, currentUser)
)
) {
return aiConversationsSidebarManager.forceCustomSidebar();
}
// newTopicForceSidebar is set to true when a new topic is created. We have
// this because the condition `postStream.posts` above will not be true as the bot response
// is not in the postStream yet when this initializer is ran. So we need to force
// the sidebar to open when creating a new topic. After that, we set it to false again.
if (aiConversationsSidebarManager.newTopicForceSidebar) {
aiConversationsSidebarManager.newTopicForceSidebar = false;
return aiConversationsSidebarManager.forceCustomSidebar();
}
aiConversationsSidebarManager.stopForcingCustomSidebar();
};
api.container
.lookup("service:router")
.on("routeDidChange", setSidebarPanel);
});
},
};

View File

@ -1,325 +0,0 @@
// Hide the new question button from the hamburger menu's footer
.hamburger-panel .ai-new-question-button {
display: none;
}
body.has-ai-conversations-sidebar {
.sidebar-wrapper {
.sidebar-footer-actions {
display: flex;
flex-direction: column;
margin-left: 0;
align-items: flex-end;
width: 100%;
.ai-new-question-button {
width: 100%;
}
}
.sidebar-container {
border: none;
}
// ai related sidebar content
[data-section-name="ai-conversations-history"] {
.sidebar-section-header-wrapper {
pointer-events: none;
font-size: var(--font-down-1);
.sidebar-section-header-caret {
display: none;
}
.sidebar-section-header-text {
letter-spacing: 0.5px;
}
}
.sidebar-section-link-wrapper {
.sidebar-section-link {
height: unset;
padding-block: 0.65em;
font-size: var(--font-down-1);
letter-spacing: 0.35px;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
.sidebar-section-link-content-text {
white-space: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
}
.sidebar-section-link-prefix {
align-self: start;
}
}
}
// topic elements
#topic-footer-button-share-and-invite,
body:not(.staff) #topic-footer-button-archive,
#topic-footer-buttons .topic-notifications-button,
.bookmark-menu-trigger,
.more-topics__container,
.private-message-glyph-wrapper,
.topic-header-participants,
.topic-above-footer-buttons-outlet,
.topic-map,
.timeline-ago,
#topic-footer-buttons .topic-footer-main-buttons details {
display: none;
}
.topic-timer-info {
border: none;
}
.topic-owner .actions .create-flag {
// why flag my own post
display: none;
}
.container.posts {
margin-bottom: 0;
.topic-navigation.with-timeline {
top: calc(var(--header-offset, 60px) + 5.5em);
}
.topic-navigation {
.topic-notifications-button {
display: none;
}
}
}
#topic-title {
display: flex;
justify-content: center;
width: 100%;
.title-wrapper {
width: 100%;
max-width: 960px;
}
}
.small-action,
.onscreen-post .row {
justify-content: center;
}
#topic-footer-buttons {
width: calc(100% - 6.5em);
margin-top: 0;
@media screen and (max-width: 924px) {
max-width: unset;
width: 100%;
}
@media screen and (min-width: 1300px) {
width: 100%;
max-width: 51em;
}
.topic-footer-main-buttons {
justify-content: flex-end;
@media screen and (min-width: 1180px) {
margin-right: 0.6em;
}
@media screen and (max-width: 924px) {
margin-right: 0.6em;
}
}
}
#topic-progress-wrapper.docked {
display: none;
}
@media screen and (max-width: 924px) {
.archetype-private_message .topic-post:last-child {
margin-bottom: 0;
}
}
nav.post-controls .actions button {
padding: 0.5em 0.65em;
&.reply {
.d-icon {
margin-right: 0.45em;
}
}
}
.topic-footer-main-buttons {
margin-left: calc(var(--topic-avatar-width) - 1.15em);
}
.ai-bot-conversations {
height: calc(100dvh - var(--header-offset) - 1.25em);
@media screen and (min-width: 675px) {
border: 1px solid var(--primary-low);
padding: 2em 2em 3em;
border-radius: var(--border-radius);
height: calc(100dvh - var(--header-offset) - 10em);
}
&__persona-selector {
display: flex;
justify-content: flex-end;
}
&__content-wrapper {
display: flex;
flex-direction: column;
box-sizing: border-box;
align-items: center;
justify-content: center;
height: 100%;
}
&__input-wrapper {
display: flex;
align-items: stretch;
gap: 0.5em;
width: 100%;
max-width: 90dvw;
margin-top: 2em;
@media screen and (min-width: 600px) {
width: 80%;
max-width: 46em;
}
.btn-primary {
align-self: end;
min-height: 2.5em;
}
#ai-bot-conversations-input {
width: 100%;
margin: 0;
resize: none;
border-radius: var(--d-button-border-radius);
max-height: 30vh;
&:focus {
outline: none;
border-color: var(--primary-high);
}
}
}
h1 {
margin-bottom: 0.45em;
max-width: 20em;
text-align: center;
font-size: var(--font-up-6);
line-height: var(--line-height-medium);
@media screen and (min-height: 300px) {
margin-top: -1em;
}
@media screen and (min-height: 600px) {
margin-top: -3em;
}
@media screen and (min-height: 900px) {
margin-top: -6em;
}
}
.ai-disclaimer {
text-align: center;
font-size: var(--font-down-1);
color: var(--primary-700);
@media screen and (min-width: 600px) {
width: 80%;
max-width: 46em;
}
}
.sidebar-footer-wrapper {
display: flex;
.powered-by-discourse {
display: block;
}
button {
display: none;
}
}
}
// composer
.reply-details .dropdown-select-box.composer-actions,
.composer-fields {
display: none;
}
// hide user stuff
.new-user-wrapper {
.user-navigation {
display: none;
}
}
.user-main .about.collapsed-info .details {
display: none;
}
.user-content {
margin-top: 0;
}
@media screen and (max-width: 600px) {
.share-ai-conversation-button {
.d-icon {
margin: 0;
}
.d-button-label {
display: none;
}
}
}
.mobile-view {
nav.post-controls .actions button.reply .d-icon {
margin: 0;
}
.search-dropdown {
display: none;
}
.sidebar-custom-sections {
display: none;
}
}
// custom user card link
.user-card-meta__profile-link {
display: block;
padding: 0.5em 0 0.25em;
.d-icon {
font-size: var(--font-down-1);
margin-right: 0.15em;
}
}
}

View File

@ -716,14 +716,6 @@ en:
5-pro: "Gemini"
mixtral-8x7B-Instruct-V0:
"1": "Mixtral-8x7B V0.1"
conversations:
header: "What can I help with?"
submit: "Submit question"
disclaimer: "Generative AI can make mistakes. Verify important information."
placeholder: "Ask a question..."
new: "New Question"
min_input_length_message: "Message must be longer than 10 characters"
messages_sidebar_title: "Conversations"
sentiments:
dashboard:
title: "Sentiment"

View File

@ -37,10 +37,6 @@ DiscourseAi::Engine.routes.draw do
get "/preview/:topic_id" => "shared_ai_conversations#preview"
end
scope module: :ai_bot, path: "/ai-bot/conversations" do
get "/" => "conversations#index"
end
scope module: :ai_bot, path: "/ai-bot/artifacts" do
get "/:id" => "artifacts#show"
get "/:id/:version" => "artifacts#show"

View File

@ -390,7 +390,3 @@ discourse_ai:
ai_rag_images_enabled:
default: false
hidden: true
ai_enable_experimental_bot_ux:
default: false
client: true

View File

@ -43,8 +43,6 @@ register_asset "stylesheets/modules/ai-bot/common/ai-persona.scss"
register_asset "stylesheets/modules/ai-bot/common/ai-discobot-discoveries.scss"
register_asset "stylesheets/modules/ai-bot/mobile/ai-persona.scss", :mobile
register_asset "stylesheets/modules/ai-bot-conversations/common.scss"
register_asset "stylesheets/modules/embeddings/common/semantic-related-topics.scss"
register_asset "stylesheets/modules/embeddings/common/semantic-search.scss"

View File

@ -1,185 +0,0 @@
# frozen_string_literal: true
RSpec.describe "AI Bot - Personal Message", type: :system do
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:composer) { PageObjects::Components::Composer.new }
let(:ai_pm_homepage) { PageObjects::Components::AiPmHomepage.new }
let(:sidebar) { PageObjects::Components::NavigationMenu::Sidebar.new }
let(:header_dropdown) { PageObjects::Components::NavigationMenu::HeaderDropdown.new }
let(:dialog) { PageObjects::Components::Dialog.new }
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:claude_2) do
Fabricate(
:llm_model,
provider: "anthropic",
url: "https://api.anthropic.com/v1/messages",
name: "claude-2",
)
end
fab!(:bot_user) do
toggle_enabled_bots(bots: [claude_2])
SiteSetting.ai_bot_enabled = true
claude_2.reload.user
end
fab!(:bot) do
persona =
AiPersona
.find(DiscourseAi::Personas::Persona.system_personas[DiscourseAi::Personas::General])
.class_instance
.new
DiscourseAi::Personas::Bot.as(bot_user, persona: persona)
end
fab!(:pm) do
Fabricate(
:private_message_topic,
title: "This is my special PM",
user: user,
topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: user),
Fabricate.build(:topic_allowed_user, user: bot_user),
],
)
end
fab!(:first_post) do
Fabricate(:post, topic: pm, user: user, post_number: 1, raw: "This is a reply by the user")
end
fab!(:second_post) do
Fabricate(:post, topic: pm, user: bot_user, post_number: 2, raw: "This is a bot reply")
end
fab!(:third_post) do
Fabricate(
:post,
topic: pm,
user: user,
post_number: 3,
raw: "This is a second reply by the user",
)
end
fab!(:topic_user) { Fabricate(:topic_user, topic: pm, user: user) }
fab!(:topic_bot_user) { Fabricate(:topic_user, topic: pm, user: bot_user) }
fab!(:persona) do
persona =
AiPersona.create!(
name: "Test Persona",
description: "A test persona",
allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]],
enabled: true,
system_prompt: "You are a helpful bot",
)
persona.create_user!
persona.update!(
default_llm_id: claude_2.id,
allow_chat_channel_mentions: true,
allow_topic_mentions: true,
)
persona
end
before do
SiteSetting.ai_enable_experimental_bot_ux = true
SiteSetting.ai_bot_enabled = true
Jobs.run_immediately!
SiteSetting.ai_bot_allowed_groups = "#{Group::AUTO_GROUPS[:trust_level_0]}"
sign_in(user)
end
it "has normal bot interaction when `ai_enable_experimental_bot_ux` is disabled" do
SiteSetting.ai_enable_experimental_bot_ux = false
visit "/"
find(".ai-bot-button").click
expect(ai_pm_homepage).to have_no_homepage
expect(composer).to be_opened
end
context "when `ai_enable_experimental_bot_ux` is enabled" do
it "renders landing page on bot click" do
visit "/"
find(".ai-bot-button").click
expect(ai_pm_homepage).to have_homepage
expect(sidebar).to be_visible
end
it "displays error when message is too short" do
visit "/"
find(".ai-bot-button").click
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 "renders sidebar even when navigation menu is set to header" do
SiteSetting.navigation_menu = "header dropdown"
visit "/"
find(".ai-bot-button").click
expect(ai_pm_homepage).to have_homepage
expect(sidebar).to be_visible
expect(header_dropdown).to be_visible
end
it "hides default content in the sidebar" do
visit "/"
find(".ai-bot-button").click
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("messages")
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 "/"
find(".ai-bot-button").click
expect(ai_pm_homepage).to have_homepage
expect(sidebar).to have_section("ai-conversations-history")
expect(sidebar).to have_section_link(pm.title)
expect(sidebar).to have_no_css("button.ai-new-question-button")
end
it "navigates to the bot conversation when clicked" do
visit "/"
find(".ai-bot-button").click
expect(ai_pm_homepage).to have_homepage
sidebar.find(
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link",
).click
expect(topic_page).to have_topic_title(pm.title)
end
it "displays sidebar and 'new question' on the topic page" do
topic_page.visit_topic(pm)
expect(sidebar).to be_visible
expect(sidebar).to have_css("button.ai-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
sidebar.find("button.ai-new-question-button").click
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
end
end

View File

@ -1,32 +0,0 @@
# frozen_string_literal: true
module PageObjects
module Components
class AiPmHomepage < PageObjects::Components::Base
HOMEPAGE_WRAPPER_CLASS = ".ai-bot-conversations__content-wrapper"
def input
page.find("#ai-bot-conversations-input")
end
def submit
page.find(".ai-conversation-submit").click
end
def has_too_short_dialog?
page.find(
".dialog-content",
text: I18n.t("js.discourse_ai.ai_bot.conversations.min_input_length_message"),
)
end
def has_homepage?
page.has_css?(HOMEPAGE_WRAPPER_CLASS)
end
def has_no_homepage?
page.has_no_css?(HOMEPAGE_WRAPPER_CLASS)
end
end
end
end

View File

@ -50,8 +50,6 @@ acceptance("AI Helper - Post Helper Menu", function (needs) {
done: false,
});
});
server.get("/discourse-ai/ai-bot/conversations.json", () => {});
});
test("displays streamed explanation", async function (assert) {

View File

@ -29,8 +29,6 @@ acceptance("Topic - Summary", function (needs) {
done: false,
});
});
server.get("/discourse-ai/ai-bot/conversations.json", () => {});
});
needs.hooks.beforeEach(() => {