Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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)
Expand All @@ -26,7 +25,7 @@ def index
pms = base_query.order(last_posted_at: :desc).offset(page * per_page).limit(per_page)

render json: {
conversations: serialize_data(pms, BasicTopicSerializer),
conversations: serialize_data(pms, ListableTopicSerializer),
meta: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all we need is last_posted_at should we instead extend BasicTopicSerializer and do something like:

class BotConversationTopicSerializer < BasicTopicSerializer
  attributes :last_posted_at
end

since ListableTopicSerializer looks like it's adding a wealth of attributes we don't really need, so doing this might keep it lightweight and avoid unnecessary db calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could... this is so light weight (overall) and we have to carry the extra extension. I think the play is to stick with Listable vs extending. Are you okay to merge as is?

Copy link
Member

@keegangeorge keegangeorge Apr 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense, you're right, it's still pretty lightweight and is probably better than carrying the extra extension. ✅ Good to merge

total: total,
page: page,
Expand Down
81 changes: 77 additions & 4 deletions assets/javascripts/initializers/ai-conversations-sidebar.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { tracked } from "@glimmer/tracking";
import { htmlSafe } from "@ember/template";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import { ajax } from "discourse/lib/ajax";
import { bind } from "discourse/lib/decorators";
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
import { withPluginApi } from "discourse/lib/plugin-api";
import { i18n } from "discourse-i18n";
import AiBotSidebarNewConversation from "../discourse/components/ai-bot-sidebar-new-conversation";
Expand Down Expand Up @@ -85,6 +87,9 @@ export default {
@tracked links = new TrackedArray();
@tracked topics = [];
@tracked hasMore = [];
@tracked loadedSevenDayLabel = false;
@tracked loadedThirtyDayLabel = false;
@tracked loadedMonthLabels = new Set();
page = 0;
isFetching = false;
totalTopicsCount = 0;
Expand Down Expand Up @@ -127,7 +132,14 @@ export default {
}

addNewPMToSidebar(topic) {
this.links = [new AiConversationLink(topic), ...this.links];
// Reset category labels since we're adding a new topic
this.loadedSevenDayLabel = false;
this.loadedThirtyDayLabel = false;
this.loadedMonthLabels.clear();

this.topics = [topic, ...this.topics];
this.buildSidebarLinks();

this.watchForTitleUpdate(topic);
}

Expand Down Expand Up @@ -206,10 +218,71 @@ export default {
this.fetchMessages(true);
}

buildSidebarLinks() {
this.links = this.topics.map(
(topic) => new AiConversationLink(topic)
groupByDate(topic) {
const now = new Date();
const lastPostedAt = new Date(topic.last_posted_at);
const daysDiff = Math.round(
(now - lastPostedAt) / (1000 * 60 * 60 * 24)
);

// Last 7 days group
if (daysDiff <= 7) {
if (!this.loadedSevenDayLabel) {
this.loadedSevenDayLabel = true;
return {
text: i18n("discourse_ai.ai_bot.conversations.last_7_days"),
classNames: "date-heading",
name: "date-heading-last-7-days",
};
}
}
// Last 30 days group
else if (daysDiff <= 30) {
if (!this.loadedThirtyDayLabel) {
this.loadedThirtyDayLabel = true;
return {
text: i18n(
"discourse_ai.ai_bot.conversations.last_30_days"
),
classNames: "date-heading",
name: "date-heading-last-30-days",
};
}
}
// Group by month for older conversations
else {
const month = lastPostedAt.getMonth();
const year = lastPostedAt.getFullYear();
const monthKey = `${year}-${month}`;

if (!this.loadedMonthLabels.has(monthKey)) {
this.loadedMonthLabels.add(monthKey);

const formattedDate = autoUpdatingRelativeAge(
new Date(topic.last_posted_at)
);

return {
text: htmlSafe(formattedDate),
classNames: "date-heading",
name: `date-heading-${monthKey}`,
};
}
}
}

buildSidebarLinks() {
// Reset date header tracking
this.loadedSevenDayLabel = false;
this.loadedThirtyDayLabel = false;
this.loadedMonthLabels.clear();

this.links = [...this.topics].flatMap((topic) => {
const dateLabel = this.groupByDate(topic);
return dateLabel
? [dateLabel, new AiConversationLink(topic)]
: [new AiConversationLink(topic)];
});
}

watchForTitleUpdate(topic) {
Expand Down
28 changes: 18 additions & 10 deletions assets/stylesheets/modules/ai-bot-conversations/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ body.has-ai-conversations-sidebar {
margin: 1.8em 1rem 0;
}

.sidebar-toggle-all-sections {
display: none;
}

.sidebar-wrapper {
.ai-conversations-panel {
padding-top: 1em;
Expand All @@ -18,19 +22,23 @@ body.has-ai-conversations-sidebar {
// 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;
}
display: none;
}

.sidebar-section-link-wrapper {
.sidebar-section-link.date-heading {
pointer-events: none;
cursor: default;
color: var(--primary-medium);
opacity: 0.8;
font-weight: 700;
margin-top: 1em;

&[data-link-name="date-heading-last-7-days"] {
margin-top: 0;
}
}

.sidebar-section-link {
height: unset;
padding-block: 0.65em;
Expand Down
2 changes: 2 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,8 @@ en:
new: "New Question"
min_input_length_message: "Message must be longer than 10 characters"
messages_sidebar_title: "Conversations"
last_7_days: "Last 7 days"
last_30_days: "Last 30 days"
sentiments:
dashboard:
title: "Sentiment"
Expand Down
28 changes: 22 additions & 6 deletions spec/system/ai_bot/homepage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
:private_message_topic,
title: "This is my special PM",
user: user,
last_posted_at: Time.zone.now,
topic_allowed_users: [
Fabricate.build(:topic_allowed_user, user: user),
Fabricate.build(:topic_allowed_user, user: bot_user),
Expand Down Expand Up @@ -150,18 +151,35 @@

expect(ai_pm_homepage).to have_homepage
expect(sidebar).to have_section("ai-conversations-history")
expect(sidebar).to have_section_link("Last 7 days")
expect(sidebar).to have_section_link(pm.title)
expect(sidebar).to have_no_css("button.ai-new-question-button")
end

it "displays last_30_days label in the sidebar" do
pm.update!(last_posted_at: Time.zone.now - 28.days)
visit "/"
header.click_bot_button

expect(ai_pm_homepage).to have_homepage
expect(sidebar).to have_section_link("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_link("Apr 2024")
end

it "navigates to the bot conversation when clicked" do
visit "/"
header.click_bot_button

expect(ai_pm_homepage).to have_homepage
sidebar.find(
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link",
).click
ai_pm_homepage.click_fist_sidebar_conversation
expect(topic_page).to have_topic_title(pm.title)
end

Expand All @@ -173,9 +191,7 @@
expect(header).to have_icon_in_bot_button(icon: "shuffle")

# Go to a PM and assert that the icon is still shuffle
sidebar.find(
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link",
).click
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
Expand Down
6 changes: 6 additions & 0 deletions spec/system/page_objects/components/ai_pm_homepage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ def click_new_question_button
page.find(".ai-new-question-button").click
end

def click_fist_sidebar_conversation
page.find(
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link:not(.date-heading)",
).click
end

def persona_selector
PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown")
end
Expand Down