This reverts commit 5fec8fe79e
.
This commit is contained in:
parent
5fec8fe79e
commit
244ec9d61e
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export default function () {
|
||||
this.route("discourse-ai-bot-conversations", {
|
||||
path: "/discourse-ai/ai-bot/conversations",
|
||||
});
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -390,7 +390,3 @@ discourse_ai:
|
|||
ai_rag_images_enabled:
|
||||
default: false
|
||||
hidden: true
|
||||
ai_enable_experimental_bot_ux:
|
||||
default: false
|
||||
client: true
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -29,8 +29,6 @@ acceptance("Topic - Summary", function (needs) {
|
|||
done: false,
|
||||
});
|
||||
});
|
||||
|
||||
server.get("/discourse-ai/ai-bot/conversations.json", () => {});
|
||||
});
|
||||
|
||||
needs.hooks.beforeEach(() => {
|
||||
|
|
Loading…
Reference in New Issue