mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-03-07 01:39:54 +00:00
Previously we waited 1 minute before automatically titling PMs The new change introduces adding a title immediately after the the llm replies Prompt was also modified to include the LLM reply in title suggestion. This helps situation like: user: tell me a joke llm: a very funy joke about horses Then the title would be "A Funny Horse Joke" Specs already covered some auto title logic, amended to also catch the new message bus message we have been sending.
199 lines
6.9 KiB
Ruby
199 lines
6.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module AiBot
|
|
USER_AGENT = "Discourse AI Bot 1.0 (https://www.discourse.org)"
|
|
|
|
class EntryPoint
|
|
Bot = Struct.new(:id, :name, :llm)
|
|
|
|
def self.all_bot_ids
|
|
mentionable_persona_user_ids =
|
|
AiPersona.mentionables.map { |mentionable| mentionable[:user_id] }
|
|
mentionable_bot_users = LlmModel.joins(:user).pluck("users.id")
|
|
|
|
mentionable_bot_users + mentionable_persona_user_ids
|
|
end
|
|
|
|
def self.find_participant_in(participant_ids)
|
|
model = LlmModel.includes(:user).where(user_id: participant_ids).last
|
|
return if model.nil?
|
|
|
|
bot_user = model.user
|
|
|
|
Bot.new(bot_user.id, bot_user.username_lower, model.name)
|
|
end
|
|
|
|
def self.find_user_from_model(model_name)
|
|
# Hack(Roman): Added this because Command R Plus had a different in the bot settings.
|
|
# Will eventually ammend it with a data migration.
|
|
name = model_name
|
|
name = "command-r-plus" if name == "cohere-command-r-plus"
|
|
|
|
LlmModel.joins(:user).where(name: name).last&.user
|
|
end
|
|
|
|
def self.enabled_user_ids_and_models_map
|
|
DB.query_hash(<<~SQL)
|
|
SELECT users.username AS username, users.id AS id, llms.name AS model_name, llms.display_name AS display_name
|
|
FROM llm_models llms
|
|
INNER JOIN users ON llms.user_id = users.id
|
|
WHERE llms.enabled_chat_bot
|
|
SQL
|
|
end
|
|
|
|
# Most errors are simply "not_allowed"
|
|
# we do not want to reveal information about this system
|
|
# the 2 exceptions are "other_people_in_pm" and "other_content_in_pm"
|
|
# in both cases you have access to the PM so we are not revealing anything
|
|
def self.ai_share_error(topic, guardian)
|
|
return nil if guardian.can_share_ai_bot_conversation?(topic)
|
|
|
|
return :not_allowed if !guardian.can_see?(topic)
|
|
|
|
# other people in PM
|
|
if topic.topic_allowed_users.where("user_id > 0 and user_id <> ?", guardian.user.id).exists?
|
|
return :other_people_in_pm
|
|
end
|
|
|
|
# other content in PM
|
|
if topic.posts.where("user_id > 0 and user_id <> ?", guardian.user.id).exists?
|
|
return :other_content_in_pm
|
|
end
|
|
|
|
:not_allowed
|
|
end
|
|
|
|
def inject_into(plugin)
|
|
plugin.register_modifier(:chat_allowed_bot_user_ids) do |user_ids, guardian|
|
|
if guardian.user
|
|
allowed_chat = AiPersona.allowed_chat(user: guardian.user)
|
|
allowed_bot_ids = allowed_chat.map { |info| info[:user_id] }
|
|
user_ids.concat(allowed_bot_ids)
|
|
end
|
|
user_ids
|
|
end
|
|
|
|
plugin.on(:site_setting_changed) do |name, _old_value, _new_value|
|
|
if name == :ai_bot_enabled || name == :discourse_ai_enabled
|
|
DiscourseAi::AiBot::SiteSettingsExtension.enable_or_disable_ai_bots
|
|
end
|
|
end
|
|
|
|
Oneboxer.register_local_handler(
|
|
"discourse_ai/ai_bot/shared_ai_conversations",
|
|
) do |url, route|
|
|
if route[:action] == "show" && share_key = route[:share_key]
|
|
if conversation = SharedAiConversation.find_by(share_key: share_key)
|
|
conversation.onebox
|
|
end
|
|
end
|
|
end
|
|
|
|
plugin.on(:reduce_excerpt) do |doc, options|
|
|
doc.css("details").remove if options && options[:strip_details]
|
|
end
|
|
|
|
plugin.register_seedfu_fixtures(
|
|
Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "ai_bot"),
|
|
)
|
|
|
|
plugin.add_to_serializer(
|
|
:current_user,
|
|
:ai_enabled_personas,
|
|
include_condition: -> do
|
|
SiteSetting.ai_bot_enabled && scope.authenticated? &&
|
|
scope.user.in_any_groups?(SiteSetting.ai_bot_allowed_groups_map)
|
|
end,
|
|
) do
|
|
DiscourseAi::AiBot::Personas::Persona
|
|
.all(user: scope.user)
|
|
.map do |persona|
|
|
{ id: persona.id, name: persona.name, description: persona.description }
|
|
end
|
|
end
|
|
|
|
plugin.add_to_serializer(
|
|
:current_user,
|
|
:can_debug_ai_bot_conversations,
|
|
include_condition: -> do
|
|
SiteSetting.ai_bot_enabled && scope.authenticated? &&
|
|
SiteSetting.ai_bot_debugging_allowed_groups.present? &&
|
|
scope.user.in_any_groups?(SiteSetting.ai_bot_debugging_allowed_groups_map)
|
|
end,
|
|
) { true }
|
|
|
|
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
|
|
bots_map = ::DiscourseAi::AiBot::EntryPoint.enabled_user_ids_and_models_map
|
|
|
|
persona_users = AiPersona.persona_users(user: scope.user)
|
|
if persona_users.present?
|
|
bots_map.concat(
|
|
persona_users.map do |persona_user|
|
|
{
|
|
"id" => persona_user[:user_id],
|
|
"username" => persona_user[:username],
|
|
"mentionable" => persona_user[:mentionable],
|
|
"is_persona" => true,
|
|
}
|
|
end,
|
|
)
|
|
end
|
|
|
|
bots_map
|
|
end
|
|
|
|
plugin.add_to_serializer(:current_user, :can_share_ai_bot_conversations) do
|
|
scope.user.in_any_groups?(SiteSetting.ai_bot_public_sharing_allowed_groups_map)
|
|
end
|
|
|
|
plugin.register_svg_icon("robot")
|
|
|
|
plugin.add_to_serializer(
|
|
:topic_view,
|
|
:ai_persona_name,
|
|
include_condition: -> { SiteSetting.ai_bot_enabled && object.topic.private_message? },
|
|
) do
|
|
id = topic.custom_fields["ai_persona_id"]
|
|
name =
|
|
DiscourseAi::AiBot::Personas::Persona.find_by(user: scope.user, id: id.to_i)&.name if id
|
|
name || topic.custom_fields["ai_persona"]
|
|
end
|
|
|
|
plugin.on(:post_created) { |post| DiscourseAi::AiBot::Playground.schedule_reply(post) }
|
|
|
|
plugin.on(:chat_message_created) do |chat_message, channel, user, context|
|
|
DiscourseAi::AiBot::Playground.schedule_chat_reply(chat_message, channel, user, context)
|
|
end
|
|
|
|
if plugin.respond_to?(:register_editable_topic_custom_field)
|
|
plugin.register_editable_topic_custom_field(:ai_persona_id)
|
|
end
|
|
|
|
plugin.on(:site_setting_changed) do |name, old_value, new_value|
|
|
if name == :ai_embeddings_model && SiteSetting.ai_embeddings_enabled? &&
|
|
new_value != old_value
|
|
RagDocumentFragment.delete_all
|
|
UploadReference
|
|
.where(target: AiPersona.all)
|
|
.each do |ref|
|
|
Jobs.enqueue(
|
|
:digest_rag_upload,
|
|
ai_persona_id: ref.target_id,
|
|
upload_id: ref.upload_id,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|