diff --git a/app/controllers/discourse_ai/ai_bot/conversations_controller.rb b/app/controllers/discourse_ai/ai_bot/conversations_controller.rb index 2951cd5b..83863baf 100644 --- a/app/controllers/discourse_ai/ai_bot/conversations_controller.rb +++ b/app/controllers/discourse_ai/ai_bot/conversations_controller.rb @@ -10,7 +10,6 @@ module DiscourseAi 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) @@ -26,7 +25,7 @@ module DiscourseAi 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: { total: total, page: page, diff --git a/assets/javascripts/initializers/ai-conversations-sidebar.js b/assets/javascripts/initializers/ai-conversations-sidebar.js index 846c25ea..a60ee284 100644 --- a/assets/javascripts/initializers/ai-conversations-sidebar.js +++ b/assets/javascripts/initializers/ai-conversations-sidebar.js @@ -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"; @@ -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; @@ -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); } @@ -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) { diff --git a/assets/stylesheets/modules/ai-bot-conversations/common.scss b/assets/stylesheets/modules/ai-bot-conversations/common.scss index aece696c..f25071e5 100644 --- a/assets/stylesheets/modules/ai-bot-conversations/common.scss +++ b/assets/stylesheets/modules/ai-bot-conversations/common.scss @@ -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; @@ -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; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e96b2a97..9ec92915 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -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" diff --git a/spec/system/ai_bot/homepage_spec.rb b/spec/system/ai_bot/homepage_spec.rb index d83d603b..d166a36f 100644 --- a/spec/system/ai_bot/homepage_spec.rb +++ b/spec/system/ai_bot/homepage_spec.rb @@ -49,6 +49,7 @@ RSpec.describe "AI Bot - Homepage", type: :system do :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), @@ -150,18 +151,35 @@ RSpec.describe "AI Bot - Homepage", type: :system do 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 @@ -173,9 +191,7 @@ RSpec.describe "AI Bot - Homepage", type: :system do 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 diff --git a/spec/system/page_objects/components/ai_pm_homepage.rb b/spec/system/page_objects/components/ai_pm_homepage.rb index 9f68e926..0257c98b 100644 --- a/spec/system/page_objects/components/ai_pm_homepage.rb +++ b/spec/system/page_objects/components/ai_pm_homepage.rb @@ -40,6 +40,12 @@ module PageObjects 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