2025-04-22 10:22:03 -05:00
|
|
|
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 { AI_CONVERSATIONS_PANEL } from "../discourse/services/ai-conversations-sidebar-manager";
|
|
|
|
|
|
|
|
export default {
|
|
|
|
name: "ai-conversations-sidebar",
|
|
|
|
|
|
|
|
initialize() {
|
|
|
|
withPluginApi((api) => {
|
|
|
|
const siteSettings = api.container.lookup("service:site-settings");
|
|
|
|
if (!siteSettings.ai_enable_experimental_bot_ux) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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(
|
|
|
|
"before-sidebar-sections",
|
|
|
|
AiBotSidebarNewConversation
|
|
|
|
);
|
|
|
|
api.addSidebarSection(
|
|
|
|
(BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => {
|
|
|
|
const AiConversationLink = class extends BaseCustomSidebarSectionLink {
|
|
|
|
route = "topic.fromParamsNear";
|
|
|
|
|
|
|
|
constructor(topic) {
|
|
|
|
super(...arguments);
|
|
|
|
this.topic = topic;
|
|
|
|
}
|
|
|
|
|
2025-04-24 11:17:24 -05:00
|
|
|
get key() {
|
|
|
|
return this.topic.id;
|
|
|
|
}
|
|
|
|
|
2025-04-22 10:22:03 -05:00
|
|
|
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();
|
|
|
|
|
2025-04-24 11:17:24 -05:00
|
|
|
appEvents.on(
|
|
|
|
"discourse-ai:bot-pm-created",
|
|
|
|
this,
|
|
|
|
"addNewPMToSidebar"
|
|
|
|
);
|
2025-04-22 10:22:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@bind
|
|
|
|
willDestroy() {
|
|
|
|
this.removeScrollListener();
|
2025-04-24 11:17:24 -05:00
|
|
|
appEvents.off(
|
|
|
|
"discourse-ai:bot-pm-created",
|
|
|
|
this,
|
|
|
|
"addNewPMToSidebar"
|
|
|
|
);
|
2025-04-22 10:22:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2025-04-24 11:17:24 -05:00
|
|
|
addNewPMToSidebar(topic) {
|
|
|
|
this.links = [new AiConversationLink(topic), ...this.links];
|
2025-04-22 10:22:03 -05:00
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
watchForTitleUpdate(topic) {
|
2025-04-24 11:17:24 -05:00
|
|
|
const channel = `/discourse-ai/ai-bot/topic/${topic.id}`;
|
2025-04-22 10:22:03 -05:00
|
|
|
const callback = this.updateTopicTitle.bind(this);
|
|
|
|
messageBus.subscribe(channel, ({ title }) => {
|
2025-04-24 11:17:24 -05:00
|
|
|
callback(topic, title);
|
2025-04-22 10:22:03 -05:00
|
|
|
messageBus.unsubscribe(channel);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-04-24 11:17:24 -05:00
|
|
|
updateTopicTitle(topic, title) {
|
|
|
|
// update the data
|
|
|
|
topic.title = title;
|
|
|
|
|
|
|
|
// force Glimmer to re-render that one link
|
|
|
|
this.links = this.links.map((link) =>
|
|
|
|
link.topic.id === topic.id
|
|
|
|
? new AiConversationLink(topic)
|
|
|
|
: link
|
2025-04-22 10:22:03 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
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 the topic is not a private message, not created by the current user,
|
|
|
|
// or doesn't have a bot response, we don't need to override sidebar
|
|
|
|
if (
|
|
|
|
topic?.archetype === "private_message" &&
|
|
|
|
topic.user_id === currentUser.id &&
|
2025-04-24 13:02:43 -05:00
|
|
|
topic.is_bot_pm
|
2025-04-22 10:22:03 -05:00
|
|
|
) {
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|