mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
feat: Add native support for CSML in agent_bot API (#4913)
This commit is contained in:
63
lib/integrations/bot_processor_service.rb
Normal file
63
lib/integrations/bot_processor_service.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
class Integrations::BotProcessorService
|
||||
pattr_initialize [:event_name!, :hook!, :event_data!]
|
||||
|
||||
def perform
|
||||
message = event_data[:message]
|
||||
return unless should_run_processor?(message)
|
||||
|
||||
process_content(message)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e, account: agent_bot).capture_exception
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_run_processor?(message)
|
||||
return if message.private?
|
||||
return unless processable_message?(message)
|
||||
return unless conversation.pending?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def conversation
|
||||
message = event_data[:message]
|
||||
@conversation ||= message.conversation
|
||||
end
|
||||
|
||||
def process_content(message)
|
||||
content = message_content(message)
|
||||
response = get_response(conversation.contact_inbox.source_id, content) if content.present?
|
||||
process_response(message, response) if response.present?
|
||||
end
|
||||
|
||||
def message_content(message)
|
||||
# TODO: might needs to change this to a way that we fetch the updated value from event data instead
|
||||
# cause the message.updated event could be that that the message was deleted
|
||||
|
||||
return message.content_attributes['submitted_values']&.first&.dig('value') if event_name == 'message.updated'
|
||||
|
||||
message.content
|
||||
end
|
||||
|
||||
def processable_message?(message)
|
||||
# TODO: change from reportable and create a dedicated method for this?
|
||||
return unless message.reportable?
|
||||
return if message.outgoing? && !processable_outgoing_message?(message)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def processable_outgoing_message?(message)
|
||||
event_name == 'message.updated' && ['input_select'].include?(message.content_type)
|
||||
end
|
||||
|
||||
def process_action(message, action)
|
||||
case action
|
||||
when 'handoff'
|
||||
message.conversation.open!
|
||||
when 'resolve'
|
||||
message.conversation.resolved!
|
||||
end
|
||||
end
|
||||
end
|
||||
142
lib/integrations/csml/processor_service.rb
Normal file
142
lib/integrations/csml/processor_service.rb
Normal file
@@ -0,0 +1,142 @@
|
||||
class Integrations::Csml::ProcessorService < Integrations::BotProcessorService
|
||||
pattr_initialize [:event_name!, :event_data!, :agent_bot!]
|
||||
|
||||
private
|
||||
|
||||
def csml_client
|
||||
@csml_client ||= CsmlEngine.new
|
||||
end
|
||||
|
||||
def get_response(session_id, content)
|
||||
csml_client.run(
|
||||
bot_payload,
|
||||
{
|
||||
client: client_params(session_id),
|
||||
payload: message_payload(content),
|
||||
metadata: metadata_params
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def client_params(session_id)
|
||||
{
|
||||
bot_id: "chatwoot-bot-#{conversation.inbox.id}",
|
||||
channel_id: "chatwoot-bot-inbox-#{conversation.inbox.id}",
|
||||
user_id: session_id
|
||||
}
|
||||
end
|
||||
|
||||
def message_payload(content)
|
||||
{
|
||||
content_type: 'text',
|
||||
content: { text: content }
|
||||
}
|
||||
end
|
||||
|
||||
def metadata_params
|
||||
{
|
||||
conversation: conversation,
|
||||
contact: conversation.contact
|
||||
}
|
||||
end
|
||||
|
||||
def bot_payload
|
||||
{
|
||||
id: "chatwoot-csml-bot-#{agent_bot.id}",
|
||||
name: "chatwoot-csml-bot-#{agent_bot.id}",
|
||||
default_flow: 'chatwoot_bot_flow',
|
||||
flows: [
|
||||
{
|
||||
id: "chatwoot-csml-bot-flow-#{agent_bot.id}-inbox-#{conversation.inbox.id}",
|
||||
name: 'chatwoot_bot_flow',
|
||||
content: agent_bot.bot_config['csml_content'],
|
||||
commands: []
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def process_response(message, response)
|
||||
csml_messages = response['messages']
|
||||
has_conversation_ended = response['conversation_end']
|
||||
|
||||
process_action(message, 'handoff') if has_conversation_ended.present?
|
||||
|
||||
return if csml_messages.blank?
|
||||
|
||||
# We do not support wait, typing now.
|
||||
csml_messages.each do |csml_message|
|
||||
create_messages(csml_message, conversation)
|
||||
end
|
||||
end
|
||||
|
||||
def create_messages(message, conversation)
|
||||
message_payload = message['payload']
|
||||
|
||||
case message_payload['content_type']
|
||||
when 'text'
|
||||
process_text_messages(message_payload, conversation)
|
||||
when 'question'
|
||||
process_question_messages(message_payload, conversation)
|
||||
when 'image'
|
||||
process_image_messages(message_payload, conversation)
|
||||
end
|
||||
end
|
||||
|
||||
def process_text_messages(message_payload, conversation)
|
||||
conversation.messages.create(
|
||||
{
|
||||
message_type: :outgoing,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
content: message_payload['content']['text'],
|
||||
sender: agent_bot
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def process_question_messages(message_payload, conversation)
|
||||
buttons = message_payload['content']['buttons'].map do |button|
|
||||
{ title: button['content']['title'], value: button['content']['payload'] }
|
||||
end
|
||||
conversation.messages.create(
|
||||
{
|
||||
message_type: :outgoing,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
content: message_payload['content']['title'],
|
||||
content_type: 'input_select',
|
||||
content_attributes: { items: buttons },
|
||||
sender: agent_bot
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def prepare_attachment(message_payload, message, account_id)
|
||||
attachment_params = { file_type: :image, account_id: account_id }
|
||||
attachment_url = message_payload['content']['url']
|
||||
attachment = message.attachments.new(attachment_params)
|
||||
attachment_file = Down.download(attachment_url)
|
||||
attachment.file.attach(
|
||||
io: attachment_file,
|
||||
filename: attachment_file.original_filename,
|
||||
content_type: attachment_file.content_type
|
||||
)
|
||||
end
|
||||
|
||||
def process_image_messages(message_payload, conversation)
|
||||
message = conversation.messages.new(
|
||||
{
|
||||
message_type: :outgoing,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
content: '',
|
||||
content_type: 'text',
|
||||
sender: agent_bot
|
||||
}
|
||||
)
|
||||
|
||||
prepare_attachment(message_payload, message, conversation.account_id)
|
||||
message.save!
|
||||
end
|
||||
end
|
||||
@@ -1,17 +1,6 @@
|
||||
class Integrations::Dialogflow::ProcessorService
|
||||
class Integrations::Dialogflow::ProcessorService < Integrations::BotProcessorService
|
||||
pattr_initialize [:event_name!, :hook!, :event_data!]
|
||||
|
||||
def perform
|
||||
message = event_data[:message]
|
||||
return if message.private?
|
||||
return unless processable_message?(message)
|
||||
return unless message.conversation.pending?
|
||||
|
||||
content = message_content(message)
|
||||
response = get_dialogflow_response(message.conversation.contact_inbox.source_id, content) if content.present?
|
||||
process_response(message, response) if response.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_content(message)
|
||||
@@ -23,19 +12,7 @@ class Integrations::Dialogflow::ProcessorService
|
||||
message.content
|
||||
end
|
||||
|
||||
def processable_message?(message)
|
||||
# TODO: change from reportable and create a dedicated method for this?
|
||||
return unless message.reportable?
|
||||
return if message.outgoing? && !processable_outgoing_message?(message)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def processable_outgoing_message?(message)
|
||||
event_name == 'message.updated' && ['input_select'].include?(message.content_type)
|
||||
end
|
||||
|
||||
def get_dialogflow_response(session_id, message)
|
||||
def get_response(session_id, message)
|
||||
Google::Cloud::Dialogflow.configure { |config| config.credentials = hook.settings['credentials'] }
|
||||
session_client = Google::Cloud::Dialogflow.sessions
|
||||
session = session_client.session_path project: hook.settings['project_id'], session: session_id
|
||||
@@ -72,13 +49,4 @@ class Integrations::Dialogflow::ProcessorService
|
||||
inbox_id: conversation.inbox_id
|
||||
}))
|
||||
end
|
||||
|
||||
def process_action(message, action)
|
||||
case action
|
||||
when 'handoff'
|
||||
message.conversation.open!
|
||||
when 'resolve'
|
||||
message.conversation.resolved!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user