diff --git a/assets/javascripts/discourse/components/ai-bot-header-panel.js b/assets/javascripts/discourse/components/ai-bot-header-panel.js
index 90e7b638..892bfdad 100644
--- a/assets/javascripts/discourse/components/ai-bot-header-panel.js
+++ b/assets/javascripts/discourse/components/ai-bot-header-panel.js
@@ -10,7 +10,7 @@ export default class AiBotHeaderPanel extends Component {
@service composer;
@action
- async composeMessageWithTargetBot(target) {
+ composeMessageWithTargetBot(target) {
this.#composeAiBotMessage(target);
}
@@ -27,7 +27,7 @@ export default class AiBotHeaderPanel extends Component {
return this.siteSettings.ai_bot_enabled_chat_bots.split("|");
}
- async #composeAiBotMessage(targetBot) {
+ #composeAiBotMessage(targetBot) {
this.args.closePanel();
composeAiBotMessage(targetBot, this.composer);
}
diff --git a/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.hbs b/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.hbs
new file mode 100644
index 00000000..02110038
--- /dev/null
+++ b/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.hbs
@@ -0,0 +1,8 @@
+{{#if this.isAiBotChat}}
+
+ {{#if this.renderChatWarning}}
+
{{i18n
+ "discourse_ai.ai_bot.pm_warning"
+ }}
+ {{/if}}
+{{/if}}
\ No newline at end of file
diff --git a/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.js b/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.js
new file mode 100644
index 00000000..a31b7188
--- /dev/null
+++ b/assets/javascripts/discourse/connectors/composer-after-composer-editor/composer-open.js
@@ -0,0 +1,32 @@
+import Component from "@glimmer/component";
+import { inject as service } from "@ember/service";
+import { computed } from "@ember/object";
+
+export default class extends Component {
+ static shouldRender() {
+ return true;
+ }
+
+ @service currentUser;
+ @service siteSettings;
+
+ get composerModel() {
+ return this.args.outletArgs.model;
+ }
+
+ get renderChatWarning() {
+ return this.siteSettings.ai_bot_enable_chat_warning;
+ }
+
+ @computed("composerModel.targetRecipients")
+ get isAiBotChat() {
+ if (this.composerModel.targetRecipients) {
+ let reciepients = this.composerModel.targetRecipients.split(",");
+
+ return this.currentUser.ai_enabled_chat_bots.any((bot) =>
+ reciepients.any((username) => username === bot.username)
+ );
+ }
+ return false;
+ }
+}
diff --git a/assets/javascripts/discourse/lib/ai-bot-helper.js b/assets/javascripts/discourse/lib/ai-bot-helper.js
index b90d03db..5ed0e19d 100644
--- a/assets/javascripts/discourse/lib/ai-bot-helper.js
+++ b/assets/javascripts/discourse/lib/ai-bot-helper.js
@@ -1,13 +1,12 @@
-import { ajax } from "discourse/lib/ajax";
import Composer from "discourse/models/composer";
import I18n from "I18n";
-export async function composeAiBotMessage(targetBot, composer) {
- let botUsername = await ajax("/discourse-ai/ai-bot/bot-username", {
- data: { username: targetBot },
- }).then((data) => {
- return data.bot_username;
- });
+export function composeAiBotMessage(targetBot, composer) {
+ const currentUser = composer.currentUser;
+
+ let botUsername = currentUser.ai_enabled_chat_bots.find(
+ (bot) => bot.model_name === targetBot
+ ).username;
composer.focusComposer({
fallbackToNewTopic: true,
diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
index fa0ce2e5..79e2a79b 100644
--- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
+++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
@@ -2,6 +2,25 @@ nav.post-controls .actions button.cancel-streaming {
display: none;
}
+.ai-bot-chat #reply-control {
+ .title-input {
+ display: none;
+ }
+}
+
+.ai-bot-chat-warning {
+ color: var(--tertiary);
+ background-color: var(--tertiary-low);
+ box-shadow: 0px 0px 0px 2px var(--tertiary-medium);
+ opacity: 0.75;
+ .d-icon {
+ color: var(--tertiary);
+ }
+ margin: 10px 2px 0;
+ padding: 4px 10px;
+ width: fit-content;
+}
+
article.streaming nav.post-controls .actions button.cancel-streaming {
display: inline-block;
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index a051eb62..6aaecdc1 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -19,6 +19,7 @@ en:
semantic_search: "Topics (Semantic)"
ai_bot:
+ pm_warning: "AI chatbot messages are monitored regularly by moderators."
cancel_streaming: "Stop reply"
default_pm_prefix: "[Untitled AI bot PM]"
shortcut_title: "Start a PM with an AI bot"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 6b9839c4..b64f9d0e 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -58,6 +58,7 @@ en:
ai_summarization_discourse_service_api_key: "API key for the Discourse summarization API."
ai_bot_enabled: "Enable the AI Bot module."
+ ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning"
ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups."
ai_bot_enabled_chat_bots: "Available models to act as an AI Bot"
ai_bot_enabled_chat_commands: "Available GPT integrations used to provide external functionality to the model. Only works with GPT-4 and GPT-3.5"
diff --git a/config/settings.yml b/config/settings.yml
index 85a08ac9..a2dce5cb 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -179,6 +179,9 @@ plugins:
ai_bot_enabled:
default: false
client: true
+ ai_bot_enable_chat_warning:
+ default: false
+ client: true
ai_bot_allowed_groups:
client: true
type: group_list
diff --git a/lib/modules/ai_bot/entry_point.rb b/lib/modules/ai_bot/entry_point.rb
index 116e520e..f2dae10a 100644
--- a/lib/modules/ai_bot/entry_point.rb
+++ b/lib/modules/ai_bot/entry_point.rb
@@ -43,6 +43,33 @@ module DiscourseAi
Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "ai_bot"),
)
+ plugin.add_to_serializer(
+ :current_user,
+ :ai_enabled_chat_bots,
+ include_condition: -> do
+ SiteSetting.ai_bot_enabled && scope.authenticated? &&
+ scope.user.in_any_groups?(SiteSetting.ai_bot_allowed_groups_map)
+ end,
+ ) do
+ model_map = {}
+ SiteSetting
+ .ai_bot_enabled_chat_bots
+ .split("|")
+ .each do |bot_name|
+ model_map[
+ ::DiscourseAi::AiBot::EntryPoint.map_bot_model_to_user_id(bot_name)
+ ] = bot_name
+ end
+
+ # not 100% ideal, cause it is one extra query, but we need it
+ bots = DB.query_hash(<<~SQL, user_ids: model_map.keys)
+ SELECT username, id FROM users WHERE id IN (:user_ids)
+ SQL
+
+ bots.each { |hash| hash["model_name"] = model_map[hash["id"]] }
+ bots
+ end
+
plugin.register_svg_icon("robot")
plugin.on(:post_created) do |post|