diff --git a/assets/javascripts/discourse/connectors/after-d-editor/composer-open.hbs b/assets/javascripts/discourse/connectors/after-d-editor/composer-open.hbs
index 02110038..d1d194d3 100644
--- a/assets/javascripts/discourse/connectors/after-d-editor/composer-open.hbs
+++ b/assets/javascripts/discourse/connectors/after-d-editor/composer-open.hbs
@@ -1,5 +1,5 @@
{{#if this.isAiBotChat}}
-
+
{{#if this.renderChatWarning}}
{{i18n
"discourse_ai.ai_bot.pm_warning"
diff --git a/assets/javascripts/discourse/connectors/after-d-editor/composer-open.js b/assets/javascripts/discourse/connectors/after-d-editor/composer-open.js
index da5633db..493d7c14 100644
--- a/assets/javascripts/discourse/connectors/after-d-editor/composer-open.js
+++ b/assets/javascripts/discourse/connectors/after-d-editor/composer-open.js
@@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { computed } from "@ember/object";
+import I18n from "discourse-i18n";
export default class extends Component {
@service currentUser;
@@ -14,6 +15,18 @@ export default class extends Component {
return this.siteSettings.ai_bot_enable_chat_warning;
}
+ @computed("composerModel.targetRecipients", "composerModel.title")
+ get aiBotClasses() {
+ if (
+ this.composerModel?.title ===
+ I18n.t("discourse_ai.ai_bot.default_pm_prefix")
+ ) {
+ return "ai-bot-chat";
+ } else {
+ return "ai-bot-pm";
+ }
+ }
+
@computed("composerModel.targetRecipients")
get isAiBotChat() {
if (
diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
index 0c487257..b712c8e1 100644
--- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
+++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
@@ -15,6 +15,18 @@ nav.post-controls .actions button.cancel-streaming {
}
}
+.ai-bot-pm {
+ .gpt-persona {
+ margin-bottom: 5px;
+ }
+ #reply-control .composer-fields {
+ .mini-tag-chooser,
+ .add-warning {
+ display: none;
+ }
+ }
+}
+
.ai-bot-chat-warning {
color: var(--tertiary);
background-color: var(--tertiary-low);
diff --git a/db/fixtures/ai_bot/602_bot_users.rb b/db/fixtures/ai_bot/602_bot_users.rb
index 8ddb024b..ae4d43f2 100644
--- a/db/fixtures/ai_bot/602_bot_users.rb
+++ b/db/fixtures/ai_bot/602_bot_users.rb
@@ -1,28 +1,3 @@
# frozen_string_literal: true
-DiscourseAi::AiBot::EntryPoint::BOTS.each do |id, bot_username|
- # let's not create a bot user if it already exists
- # seed seems to be messing with dates on the user
- # causing it to look like these bots were created at the
- # wrong time
- if !User.exists?(id: id)
- UserEmail.seed do |ue|
- ue.id = id
- ue.email = "no_email_#{bot_username}"
- ue.primary = true
- ue.user_id = id
- end
-
- User.seed do |u|
- u.id = id
- u.name = bot_username.titleize
- u.username = UserNameSuggester.suggest(bot_username)
- u.password = SecureRandom.hex
- u.active = true
- u.admin = true
- u.moderator = true
- u.approved = true
- u.trust_level = TrustLevel[4]
- end
- end
-end
+DiscourseAi::AiBot::SiteSettingsExtension.enable_or_disable_ai_bots
diff --git a/lib/modules/ai_bot/entry_point.rb b/lib/modules/ai_bot/entry_point.rb
index 3ab557c7..660d908a 100644
--- a/lib/modules/ai_bot/entry_point.rb
+++ b/lib/modules/ai_bot/entry_point.rb
@@ -8,7 +8,11 @@ module DiscourseAi
GPT4_ID = -110
GPT3_5_TURBO_ID = -111
CLAUDE_V2_ID = -112
- BOTS = [[GPT4_ID, "gpt4_bot"], [GPT3_5_TURBO_ID, "gpt3.5_bot"], [CLAUDE_V2_ID, "claude_bot"]]
+ BOTS = [
+ [GPT4_ID, "gpt4_bot", "gpt-4"],
+ [GPT3_5_TURBO_ID, "gpt3.5_bot", "gpt-3.5-turbo"],
+ [CLAUDE_V2_ID, "claude_bot", "claude-2"],
+ ]
def self.map_bot_model_to_user_id(model_name)
case model_name
@@ -48,9 +52,16 @@ module DiscourseAi
require_relative "personas/settings_explorer"
require_relative "personas/researcher"
require_relative "personas/creative"
+ require_relative "site_settings_extension"
end
def inject_into(plugin)
+ plugin.on(:site_setting_changed) do |name, _old_value, _new_value|
+ if name == :ai_bot_enabled_chat_bots || name == :ai_bot_enabled
+ DiscourseAi::AiBot::SiteSettingsExtension.enable_or_disable_ai_bots
+ end
+ end
+
plugin.register_seedfu_fixtures(
Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "ai_bot"),
)
diff --git a/lib/modules/ai_bot/site_settings_extension.rb b/lib/modules/ai_bot/site_settings_extension.rb
new file mode 100644
index 00000000..77f17641
--- /dev/null
+++ b/lib/modules/ai_bot/site_settings_extension.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module DiscourseAi::AiBot::SiteSettingsExtension
+ def self.enable_or_disable_ai_bots
+ enabled_bots = SiteSetting.ai_bot_enabled_chat_bots_map
+ enabled_bots = [] if !SiteSetting.ai_bot_enabled
+ DiscourseAi::AiBot::EntryPoint::BOTS.each do |id, bot_name, name|
+ active = enabled_bots.include?(name)
+ user = User.find_by(id: id)
+
+ if active
+ if !user
+ user =
+ User.new(
+ id: id,
+ email: "no_email_#{name}",
+ name: bot_name.titleize,
+ username: UserNameSuggester.suggest(bot_name),
+ active: true,
+ approved: true,
+ admin: true,
+ moderator: true,
+ trust_level: TrustLevel[4],
+ )
+ user.save!(validate: false)
+ else
+ user.update!(active: true)
+ end
+ elsif !active && user
+ # will include deleted
+ has_posts = DB.query_single("SELECT 1 FROM posts WHERE user_id = #{id} LIMIT 1").present?
+
+ if has_posts
+ user.update!(active: false) if user.active
+ else
+ user.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/modules/ai_bot/anthropic_bot_spec.rb b/spec/lib/modules/ai_bot/anthropic_bot_spec.rb
index 73d4838e..8d1411d4 100644
--- a/spec/lib/modules/ai_bot/anthropic_bot_spec.rb
+++ b/spec/lib/modules/ai_bot/anthropic_bot_spec.rb
@@ -7,6 +7,11 @@ module ::DiscourseAi
User.find(EntryPoint::CLAUDE_V2_ID)
end
+ before do
+ SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
+ SiteSetting.ai_bot_enabled = true
+ end
+
let(:bot) { described_class.new(bot_user) }
let(:post) { Fabricate(:post) }
diff --git a/spec/lib/modules/ai_bot/bot_spec.rb b/spec/lib/modules/ai_bot/bot_spec.rb
index 41427fbc..3a2d654c 100644
--- a/spec/lib/modules/ai_bot/bot_spec.rb
+++ b/spec/lib/modules/ai_bot/bot_spec.rb
@@ -39,7 +39,12 @@ class FakeBot < DiscourseAi::AiBot::Bot
end
describe FakeBot do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
+ before do
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+ SiteSetting.ai_bot_enabled = true
+ end
+
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
fab!(:post) { Fabricate(:post, raw: "hello world") }
it "can handle command truncation for long messages" do
@@ -78,11 +83,16 @@ describe FakeBot do
end
describe DiscourseAi::AiBot::Bot do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
- fab!(:bot) { described_class.as(bot_user) }
+ before do
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+ SiteSetting.ai_bot_enabled = true
+ end
+
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID) }
+ let(:bot) { described_class.as(bot_user) }
fab!(:user) { Fabricate(:user) }
- fab!(:pm) do
+ let!(:pm) do
Fabricate(
:private_message_topic,
title: "This is my special PM",
@@ -93,8 +103,8 @@ describe DiscourseAi::AiBot::Bot do
],
)
end
- fab!(:first_post) { Fabricate(:post, topic: pm, user: user, raw: "This is a reply by the user") }
- fab!(:second_post) do
+ let!(:first_post) { Fabricate(:post, topic: pm, user: user, raw: "This is a reply by the user") }
+ let!(:second_post) do
Fabricate(:post, topic: pm, user: user, raw: "This is a second reply by the user")
end
diff --git a/spec/lib/modules/ai_bot/commands/command_spec.rb b/spec/lib/modules/ai_bot/commands/command_spec.rb
index 66dd6b05..e61561ee 100644
--- a/spec/lib/modules/ai_bot/commands/command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/command_spec.rb
@@ -3,9 +3,11 @@
require_relative "../../../../support/openai_completions_inference_stubs"
RSpec.describe DiscourseAi::AiBot::Commands::Command do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
let(:command) { DiscourseAi::AiBot::Commands::GoogleCommand.new(bot_user: bot_user, args: nil) }
+ before { SiteSetting.ai_bot_enabled = true }
+
describe "#format_results" do
it "can generate efficient tables of data" do
rows = [1, 2, 3, 4, 5]
diff --git a/spec/lib/modules/ai_bot/commands/google_command_spec.rb b/spec/lib/modules/ai_bot/commands/google_command_spec.rb
index 68e3fc3d..f932cef3 100644
--- a/spec/lib/modules/ai_bot/commands/google_command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/google_command_spec.rb
@@ -1,7 +1,9 @@
#frozen_string_literal: true
RSpec.describe DiscourseAi::AiBot::Commands::GoogleCommand do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+
+ before { SiteSetting.ai_bot_enabled = true }
describe "#process" do
it "will not explode if there are no results" do
diff --git a/spec/lib/modules/ai_bot/commands/image_command_spec.rb b/spec/lib/modules/ai_bot/commands/image_command_spec.rb
index 1d780d32..c2fc7b81 100644
--- a/spec/lib/modules/ai_bot/commands/image_command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/image_command_spec.rb
@@ -3,7 +3,9 @@
require_relative "../../../../support/stable_difussion_stubs"
RSpec.describe DiscourseAi::AiBot::Commands::ImageCommand do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+
+ before { SiteSetting.ai_bot_enabled = true }
describe "#process" do
it "can generate correct info" do
diff --git a/spec/lib/modules/ai_bot/commands/read_command_spec.rb b/spec/lib/modules/ai_bot/commands/read_command_spec.rb
index dc100bc0..26b04d81 100644
--- a/spec/lib/modules/ai_bot/commands/read_command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/read_command_spec.rb
@@ -1,7 +1,7 @@
#frozen_string_literal: true
RSpec.describe DiscourseAi::AiBot::Commands::ReadCommand do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
fab!(:parent_category) { Fabricate(:category, name: "animals") }
fab!(:category) { Fabricate(:category, parent_category: parent_category, name: "amazing-cat") }
@@ -22,6 +22,8 @@ RSpec.describe DiscourseAi::AiBot::Commands::ReadCommand do
Fabricate(:topic, category: category, tags: [tag_funny, tag_sad, tag_hidden])
end
+ before { SiteSetting.ai_bot_enabled = true }
+
describe "#process" do
it "can read a topic" do
topic_id = topic_with_tags.id
diff --git a/spec/lib/modules/ai_bot/commands/search_command_spec.rb b/spec/lib/modules/ai_bot/commands/search_command_spec.rb
index ba8728e6..2c43ce4a 100644
--- a/spec/lib/modules/ai_bot/commands/search_command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/search_command_spec.rb
@@ -28,6 +28,8 @@ RSpec.describe DiscourseAi::AiBot::Commands::SearchCommand do
Fabricate(:topic, category: category, tags: [tag_funny, tag_sad, tag_hidden])
end
+ before { SiteSetting.ai_bot_enabled = true }
+
describe "#process" do
it "can handle no results" do
post1 = Fabricate(:post, topic: topic_with_tags)
diff --git a/spec/lib/modules/ai_bot/commands/summarize_command_spec.rb b/spec/lib/modules/ai_bot/commands/summarize_command_spec.rb
index 5b7888a0..078a288d 100644
--- a/spec/lib/modules/ai_bot/commands/summarize_command_spec.rb
+++ b/spec/lib/modules/ai_bot/commands/summarize_command_spec.rb
@@ -3,7 +3,9 @@
require_relative "../../../../support/openai_completions_inference_stubs"
RSpec.describe DiscourseAi::AiBot::Commands::SummarizeCommand do
- fab!(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+ let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID) }
+
+ before { SiteSetting.ai_bot_enabled = true }
describe "#process" do
it "can generate correct info" do
diff --git a/spec/lib/modules/ai_bot/entry_point_spec.rb b/spec/lib/modules/ai_bot/entry_point_spec.rb
index d6d04ebd..b3326a54 100644
--- a/spec/lib/modules/ai_bot/entry_point_spec.rb
+++ b/spec/lib/modules/ai_bot/entry_point_spec.rb
@@ -17,6 +17,8 @@ RSpec.describe DiscourseAi::AiBot::EntryPoint do
end
before do
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4|claude-2"
+ SiteSetting.ai_bot_enabled = true
SiteSetting.ai_bot_allowed_groups = bot_allowed_group.id
bot_allowed_group.add(admin)
end
diff --git a/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb b/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb
index 95b1f3de..212b2e00 100644
--- a/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb
+++ b/spec/lib/modules/ai_bot/jobs/regular/create_ai_reply_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Jobs::CreateAiReply do
before do
# got to do this cause we include times in system message
freeze_time
+ SiteSetting.ai_bot_enabled = true
end
describe "#execute" do
@@ -78,6 +79,7 @@ RSpec.describe Jobs::CreateAiReply do
let(:deltas) { claude_response.split(" ").map { |w| "#{w} " } }
before do
+ SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
bot_user = User.find(DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)
AnthropicCompletionStubs.stub_streamed_response(
diff --git a/spec/lib/modules/ai_bot/jobs/regular/update_ai_bot_pm_title_spec.rb b/spec/lib/modules/ai_bot/jobs/regular/update_ai_bot_pm_title_spec.rb
index 1916c268..4477e7da 100644
--- a/spec/lib/modules/ai_bot/jobs/regular/update_ai_bot_pm_title_spec.rb
+++ b/spec/lib/modules/ai_bot/jobs/regular/update_ai_bot_pm_title_spec.rb
@@ -4,6 +4,11 @@ RSpec.describe Jobs::UpdateAiBotPmTitle do
let(:user) { Fabricate(:admin) }
let(:bot_user) { User.find(DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID) }
+ before do
+ SiteSetting.ai_bot_enabled_chat_bots = "claude-2"
+ SiteSetting.ai_bot_enabled = true
+ end
+
it "will properly update title on bot PMs" do
SiteSetting.ai_bot_allowed_groups = Group::AUTO_GROUPS[:staff]
diff --git a/spec/lib/modules/ai_bot/open_ai_bot_spec.rb b/spec/lib/modules/ai_bot/open_ai_bot_spec.rb
index 6d591496..f515c991 100644
--- a/spec/lib/modules/ai_bot/open_ai_bot_spec.rb
+++ b/spec/lib/modules/ai_bot/open_ai_bot_spec.rb
@@ -14,6 +14,11 @@ RSpec.describe DiscourseAi::AiBot::OpenAiBot do
subject { described_class.new(bot_user) }
+ before do
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+ SiteSetting.ai_bot_enabled = true
+ end
+
context "when changing available commands" do
it "contains all commands by default" do
# this will break as we add commands, but it is important as a sanity check
@@ -69,11 +74,11 @@ RSpec.describe DiscourseAi::AiBot::OpenAiBot do
end
context "when the topic has multiple posts" do
- fab!(:post_1) { Fabricate(:post, topic: topic, raw: post_body(1), post_number: 1) }
- fab!(:post_2) do
+ let!(:post_1) { Fabricate(:post, topic: topic, raw: post_body(1), post_number: 1) }
+ let!(:post_2) do
Fabricate(:post, topic: topic, user: bot_user, raw: post_body(2), post_number: 2)
end
- fab!(:post_3) { Fabricate(:post, topic: topic, raw: post_body(3), post_number: 3) }
+ let!(:post_3) { Fabricate(:post, topic: topic, raw: post_body(3), post_number: 3) }
it "includes them in the prompt respecting the post number order" do
prompt_messages = subject.bot_prompt_with_topic_context(post_3)
diff --git a/spec/lib/modules/ai_bot/site_setting_extension_spec.rb b/spec/lib/modules/ai_bot/site_setting_extension_spec.rb
new file mode 100644
index 00000000..7b4144cd
--- /dev/null
+++ b/spec/lib/modules/ai_bot/site_setting_extension_spec.rb
@@ -0,0 +1,50 @@
+#frozen_string_literal: true
+
+describe DiscourseAi::AiBot::SiteSettingsExtension do
+ it "correctly creates/deletes bot accounts as needed" do
+ SiteSetting.ai_bot_enabled = true
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT4_ID)).to eq(true)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID)).to eq(false)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)).to eq(false)
+
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-3.5-turbo"
+
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT4_ID)).to eq(false)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID)).to eq(true)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)).to eq(false)
+
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-3.5-turbo|claude-2"
+
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT4_ID)).to eq(false)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID)).to eq(true)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)).to eq(true)
+
+ SiteSetting.ai_bot_enabled = false
+
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT4_ID)).to eq(false)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID)).to eq(false)
+ expect(User.exists?(id: DiscourseAi::AiBot::EntryPoint::CLAUDE_V2_ID)).to eq(false)
+ end
+
+ it "leaves accounts around if they have any posts" do
+ SiteSetting.ai_bot_enabled = true
+ SiteSetting.ai_bot_enabled_chat_bots = "gpt-4"
+
+ user = User.find(DiscourseAi::AiBot::EntryPoint::GPT4_ID)
+
+ create_post(user: user, raw: "this is a test post")
+
+ user.reload
+ SiteSetting.ai_bot_enabled = false
+
+ user.reload
+ expect(user.active).to eq(false)
+
+ SiteSetting.ai_bot_enabled = true
+
+ user.reload
+ expect(user.active).to eq(true)
+ end
+end
diff --git a/spec/requests/ai_bot/bot_controller_spec.rb b/spec/requests/ai_bot/bot_controller_spec.rb
index 8cebaa7c..32722e46 100644
--- a/spec/requests/ai_bot/bot_controller_spec.rb
+++ b/spec/requests/ai_bot/bot_controller_spec.rb
@@ -30,6 +30,7 @@ RSpec.describe DiscourseAi::AiBot::BotController do
describe "#show_bot_username" do
it "returns the username_lower of the selected bot" do
+ SiteSetting.ai_bot_enabled = true
gpt_3_5_bot = "gpt-3.5-turbo"
expected_username = User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID).username_lower