Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit bb6af2a

Browse files
committed
Mega WIP version
1 parent ed907dd commit bb6af2a

File tree

15 files changed

+881
-0
lines changed

15 files changed

+881
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module AiBot
5+
class ConversationsController < ::ApplicationController
6+
requires_plugin ::DiscourseAi::PLUGIN_NAME
7+
requires_login
8+
9+
def index
10+
# Step 1: Retrieve all AI bot user IDs
11+
bot_user_ids = EntryPoint.all_bot_ids
12+
13+
# Step 2: Query for PM topics including current_user and any bot ID
14+
pms =
15+
Topic
16+
.joins(:topic_users)
17+
.private_messages
18+
.where("topic_users.user_id IN (?)", bot_user_ids + [current_user.id])
19+
.group("topics.id") # Group by topic to ensure distinct results
20+
.having("COUNT(topic_users.user_id) > 1") # Ensure multiple participants in the PM
21+
22+
# Step 3: Serialize (empty array if no results)
23+
serialized_pms = serialize_data(pms, BasicTopicSerializer)
24+
25+
render json: serialized_pms, status: 200
26+
end
27+
end
28+
end
29+
end

assets/javascripts/discourse/components/ai-bot-header-icon.gjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default class AiBotHeaderIcon extends Component {
99
@service currentUser;
1010
@service siteSettings;
1111
@service composer;
12+
@service router;
1213

1314
get bots() {
1415
const availableBots = this.currentUser.ai_enabled_chat_bots
@@ -24,6 +25,9 @@ export default class AiBotHeaderIcon extends Component {
2425

2526
@action
2627
compose() {
28+
if (this.siteSettings.ai_enable_experimental_bot_ux) {
29+
return this.router.transitionTo("discourse-ai-bot-conversations");
30+
}
2731
composeAiBotMessage(this.bots[0], this.composer);
2832
}
2933

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Component from "@glimmer/component";
2+
import { service } from "@ember/service";
3+
import DButton from "discourse/components/d-button";
4+
import { i18n } from "discourse-i18n";
5+
6+
export default class AiBotSidebarNewConversation extends Component {
7+
@service router;
8+
9+
get show() {
10+
return this.router.currentRouteName !== "discourse-ai-bot-conversations";
11+
}
12+
13+
<template>
14+
{{#if this.show}}
15+
<DButton
16+
@route="/discourse-ai/ai-bot/conversations"
17+
@translatedLabel="TODO: new_question"
18+
@icon="plus"
19+
class="ai-new-question-button btn-default"
20+
/>
21+
{{/if}}
22+
</template>
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import bodyClass from "discourse/helpers/body-class";
2+
import Component from "@glimmer/component";
3+
4+
export default class AiBotConversaion extends Component {
5+
get show() {
6+
return this.args.outletArgs.model?.ai_persona_name
7+
}
8+
9+
<template>
10+
{{#if this.show}}
11+
{{bodyClass "discourse-ai-bot-conversations-page"}}
12+
{{/if}}
13+
</template>
14+
}
15+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Controller from "@ember/controller";
2+
import { on } from "@ember/modifier";
3+
import { computed } from "@ember/object";
4+
import { action } from "@ember/object";
5+
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
6+
import { service } from "@ember/service";
7+
import DButton from "discourse/components/d-button";
8+
import bodyClass from "discourse/helpers/body-class";
9+
import { i18n } from "discourse-i18n";
10+
import SimpleTextareaInteractor from "../lib/simple-textarea-interactor";
11+
12+
export default class DiscourseAiBotConversations extends Controller {
13+
@service aiBotConversationsHiddenSubmit;
14+
15+
textareaInteractor = null;
16+
17+
@action
18+
updateInputValue(event) {
19+
this.aiBotConversationsHiddenSubmit.inputValue = event.target.value;
20+
}
21+
22+
@action
23+
handleKeyDown(event) {
24+
if (event.key === "Enter" && !event.shiftKey) {
25+
this.aiBotConversationsHiddenSubmit.submitToBot();
26+
}
27+
}
28+
29+
@action
30+
initializeTextarea(element) {
31+
this.textareaInteractor = new SimpleTextareaInteractor(element);
32+
}
33+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function () {
2+
this.route("discourse-ai-bot-conversations", {
3+
path: "/discourse-ai/ai-bot/conversations",
4+
});
5+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { schedule } from "@ember/runloop";
2+
3+
export default class SimpleTextareaInteractor {
4+
// lifted from "discourse/plugins/chat/discourse/lib/textarea-interactor"
5+
// because the chat plugin isn't active on this site
6+
constructor(textarea) {
7+
this.textarea = textarea;
8+
this.init();
9+
this.refreshHeightBound = this.refreshHeight.bind(this);
10+
this.textarea.addEventListener("input", this.refreshHeightBound);
11+
}
12+
13+
init() {
14+
schedule("afterRender", () => {
15+
this.refreshHeight();
16+
});
17+
}
18+
19+
teardown() {
20+
this.textarea.removeEventListener("input", this.refreshHeightBound);
21+
}
22+
23+
refreshHeight() {
24+
schedule("afterRender", () => {
25+
this.textarea.style.height = "auto";
26+
this.textarea.style.height = `${this.textarea.scrollHeight + 2}px`;
27+
});
28+
}
29+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import DiscourseRoute from "discourse/routes/discourse";
2+
3+
export default class DiscourseAiBotConversationsRoute extends DiscourseRoute {}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { action } from "@ember/object";
2+
import { next } from "@ember/runloop";
3+
import Service, { service } from "@ember/service";
4+
import Composer from "discourse/models/composer";
5+
import { i18n } from "discourse-i18n";
6+
7+
export default class AiBotConversationsHiddenSubmit extends Service {
8+
@service composer;
9+
@service dialog;
10+
11+
inputValue = "";
12+
13+
@action
14+
focusInput() {
15+
this.composer.destroyDraft();
16+
this.composer.close();
17+
next(() => {
18+
document.getElementById("custom-homepage-input").focus();
19+
});
20+
}
21+
22+
@action
23+
async submitToBot() {
24+
this.composer.destroyDraft();
25+
this.composer.close();
26+
27+
if (this.inputValue.length < 10) {
28+
// TODO: Translate
29+
this.dialog.alert({
30+
message: "Message must be longer than 10 characters",
31+
didConfirm: () => this.focusInput(),
32+
didCancel: () => this.focusInput(),
33+
});
34+
}
35+
36+
// this is a total hack, the composer is hidden on the homepage with CSS
37+
await this.composer.open({
38+
action: Composer.PRIVATE_MESSAGE,
39+
draftKey: "private_message_ai",
40+
recipients: "DiscourseHelper", // TODO: Figure out how to grab the right bot
41+
topicTitle: i18n("discourse_ai.ai_bot.default_pm_prefix"),
42+
topicBody: this.inputValue,
43+
archetypeId: "private_message",
44+
disableDrafts: true,
45+
});
46+
47+
try {
48+
await this.composer.save();
49+
if (this.inputValue.length > 10) {
50+
// prevents submitting same message again when returning home
51+
// but avoids deleting too-short message on submit
52+
this.inputValue = "";
53+
}
54+
} catch (error) {
55+
// eslint-disable-next-line no-console
56+
console.error("Failed to submit message:", error);
57+
}
58+
}
59+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{{body-class "discourse-ai-bot-conversations-page"}}
2+
3+
<div class="custom-homepage__content-wrapper">
4+
<h1>Ask a question</h1>
5+
<div class="custom-homepage__input-wrapper">
6+
<textarea
7+
{{didInsert this.initializeTextarea}}
8+
{{on "input" this.updateInputValue}}
9+
{{on "keydown" this.handleKeyDown}}
10+
id="custom-homepage-input"
11+
placeholder="placeholder (todo)"
12+
minlength="10"
13+
rows="1"
14+
/>
15+
<DButton
16+
@action={{this.aiBotConversationsHiddenSubmit.submitToBot}}
17+
@icon="paper-plane"
18+
@translatedTitle="Submit (todo)"
19+
class="ai-bot-button btn-primary"
20+
/>
21+
</div>
22+
<p class="ai-disclaimer">
23+
TODO DISCLAIMER
24+
</p>
25+
</div>

0 commit comments

Comments
 (0)