feat: Improve Captain interactions, activity messages (#11493)
Show captain messages under the name of the assistant which generated the message. - Add support for `Captain::Assistant` sender type - Add push_event_data for captain_assistants - Add activity message handler for captain_assistants - Update UI to show captain messages under the name of the assistant - Fix the issue where openAI errors when image is sent - Add support for custom name of the assistant --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -6,11 +6,15 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
@inbox = conversation.inbox
|
||||
@assistant = assistant
|
||||
|
||||
Current.executed_by = @assistant
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
generate_and_process_response
|
||||
end
|
||||
rescue StandardError => e
|
||||
handle_error(e)
|
||||
ensure
|
||||
Current.executed_by = nil
|
||||
end
|
||||
|
||||
private
|
||||
@@ -37,13 +41,23 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
.where(private: false)
|
||||
.map do |message|
|
||||
{
|
||||
content: message.content,
|
||||
content: message_content(message),
|
||||
role: determine_role(message)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def message_content(message)
|
||||
return message.content if message.content.present?
|
||||
|
||||
'User has shared an attachment' if message.attachments.any?
|
||||
|
||||
'User has shared a message without content'
|
||||
end
|
||||
|
||||
def determine_role(message)
|
||||
return 'system' if message.content.blank?
|
||||
|
||||
message.message_type == 'incoming' ? 'user' : 'system'
|
||||
end
|
||||
|
||||
@@ -54,13 +68,15 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
def process_action(action)
|
||||
case action
|
||||
when 'handoff'
|
||||
create_handoff_message
|
||||
@conversation.bot_handoff!
|
||||
I18n.with_locale(@assistant.account.locale) do
|
||||
create_handoff_message
|
||||
@conversation.bot_handoff!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_handoff_message
|
||||
create_outgoing_message(@assistant.config['handoff_message'] || 'Transferring to another agent for further assistance.')
|
||||
create_outgoing_message(@assistant.config['handoff_message'].presence || I18n.t('conversations.captain.handoff'))
|
||||
end
|
||||
|
||||
def create_messages
|
||||
@@ -77,6 +93,7 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
message_type: :outgoing,
|
||||
account_id: account.id,
|
||||
inbox_id: inbox.id,
|
||||
sender: @assistant,
|
||||
content: message_content
|
||||
)
|
||||
end
|
||||
|
||||
@@ -2,22 +2,31 @@ class Captain::InboxPendingConversationsResolutionJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform(inbox)
|
||||
# limiting the number of conversations to be resolved to avoid any performance issues
|
||||
Current.executed_by = inbox.captain_assistant
|
||||
|
||||
resolvable_conversations = inbox.conversations.pending.where('last_activity_at < ? ', Time.now.utc - 1.hour).limit(Limits::BULK_ACTIONS_LIMIT)
|
||||
resolvable_conversations.each do |conversation|
|
||||
resolution_message = conversation.inbox.captain_assistant.config['resolution_message']
|
||||
create_outgoing_message(conversation, inbox)
|
||||
conversation.resolved!
|
||||
end
|
||||
ensure
|
||||
Current.reset
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_outgoing_message(conversation, inbox)
|
||||
I18n.with_locale(inbox.account.locale) do
|
||||
resolution_message = inbox.captain_assistant.config['resolution_message']
|
||||
conversation.messages.create!(
|
||||
{
|
||||
message_type: :outgoing,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
content: resolution_message || I18n.t('conversations.activity.auto_resolution_message')
|
||||
content: resolution_message.presence || I18n.t('conversations.activity.auto_resolution_message'),
|
||||
sender: inbox.captain_assistant
|
||||
}
|
||||
)
|
||||
conversation.resolved!
|
||||
ensure
|
||||
Current.reset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# index_captain_assistants_on_account_id (account_id)
|
||||
#
|
||||
class Captain::Assistant < ApplicationRecord
|
||||
include Avatarable
|
||||
|
||||
self.table_name = 'captain_assistants'
|
||||
|
||||
belongs_to :account
|
||||
@@ -26,6 +28,7 @@ class Captain::Assistant < ApplicationRecord
|
||||
dependent: :destroy_async
|
||||
has_many :inboxes,
|
||||
through: :captain_inboxes
|
||||
has_many :messages, as: :sender, dependent: :nullify
|
||||
|
||||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
@@ -34,4 +37,32 @@ class Captain::Assistant < ApplicationRecord
|
||||
scope :ordered, -> { order(created_at: :desc) }
|
||||
|
||||
scope :for_account, ->(account_id) { where(account_id: account_id) }
|
||||
|
||||
def push_event_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
avatar_url: avatar_url.presence || default_avatar_url,
|
||||
description: description,
|
||||
created_at: created_at,
|
||||
type: 'captain_assistant'
|
||||
}
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
name: name,
|
||||
avatar_url: avatar_url.presence || default_avatar_url,
|
||||
description: description,
|
||||
created_at: created_at,
|
||||
type: 'captain_assistant'
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_avatar_url
|
||||
"#{ENV.fetch('FRONTEND_URL', nil)}/assets/images/dashboard/captain/logo.svg"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
module Enterprise::ActivityMessageHandler
|
||||
def automation_status_change_activity_content
|
||||
if Current.executed_by.instance_of?(Captain::Assistant) && resolved?
|
||||
I18n.t('conversations.activity.captain.resolved', user_name: Current.executed_by.name)
|
||||
if Current.executed_by.instance_of?(Captain::Assistant)
|
||||
locale = Current.executed_by.account.locale
|
||||
if resolved?
|
||||
I18n.t(
|
||||
'conversations.activity.captain.resolved',
|
||||
user_name: Current.executed_by.name,
|
||||
locale: locale
|
||||
)
|
||||
elsif open?
|
||||
I18n.t(
|
||||
'conversations.activity.captain.open',
|
||||
user_name: Current.executed_by.name,
|
||||
locale: locale
|
||||
)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
content: Captain::Llm::SystemPromptsService.assistant_response_generator(@assistant.config['product_name'], @assistant.config)
|
||||
content: Captain::Llm::SystemPromptsService.assistant_response_generator(@assistant.name, @assistant.config['product_name'], @assistant.config)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -103,10 +103,10 @@ class Captain::Llm::SystemPromptsService
|
||||
SYSTEM_PROMPT_MESSAGE
|
||||
end
|
||||
|
||||
def assistant_response_generator(product_name, config = {})
|
||||
def assistant_response_generator(assistant_name, product_name, config = {})
|
||||
<<~SYSTEM_PROMPT_MESSAGE
|
||||
[Identity]
|
||||
You are Captain, a helpful, friendly, and knowledgeable assistant for the product #{product_name}. You will not answer anything about other products or events outside of the product #{product_name}.
|
||||
Your name is #{assistant_name || 'Captain'}, a helpful, friendly, and knowledgeable assistant for the product #{product_name}. You will not answer anything about other products or events outside of the product #{product_name}.
|
||||
|
||||
[Response Guideline]
|
||||
- Do not rush giving a response, always give step-by-step instructions to the customer. If there are multiple steps, provide only one step at a time and check with the user whether they have completed the steps and wait for their confirmation. If the user has said okay or yes, continue with the steps.
|
||||
|
||||
Reference in New Issue
Block a user