FEATURE: optional warning attached to all AI bot conversations (#137)

* FEATURE: optional warning attached to all AI bot conversations

This commit introduces `ai_bot_enable_chat_warning` which can be used
to warn people prior to starting a chat with the bot.

In particular this is useful if moderators are regularly reading chat
transcripts as it sets expectations early.

By default this is disabled.

Also:

- Stops making ajax call prior to opening composer
- Hides PM title when starting a bot PM

Co-authored-by: Rafael dos Santos Silva <xfalcox@gmail.com>
This commit is contained in:
Sam 2023-08-17 06:29:58 +10:00 committed by GitHub
parent 49f2453c2d
commit 01f833f86e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 9 deletions

View File

@ -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);
}

View File

@ -0,0 +1,8 @@
{{#if this.isAiBotChat}}
<DSection @bodyClass="ai-bot-chat" />
{{#if this.renderChatWarning}}
<div class="ai-bot-chat-warning">{{i18n
"discourse_ai.ai_bot.pm_warning"
}}</div>
{{/if}}
{{/if}}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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;
}

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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|