Merge branch 'develop' into feat/conversation-finder-strategies
This commit is contained in:
@@ -21,6 +21,10 @@ const props = defineProps({
|
||||
enableCannedResponses: { type: Boolean, default: true },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
enableCaptainTools: { type: Boolean, default: false },
|
||||
signature: { type: String, default: '' },
|
||||
allowSignature: { type: Boolean, default: false },
|
||||
sendWithSignature: { type: Boolean, default: false },
|
||||
channelType: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
@@ -100,6 +104,10 @@ watch(
|
||||
:enable-canned-responses="enableCannedResponses"
|
||||
:enabled-menu-options="enabledMenuOptions"
|
||||
:enable-captain-tools="enableCaptainTools"
|
||||
:signature="signature"
|
||||
:allow-signature="allowSignature"
|
||||
:send-with-signature="sendWithSignature"
|
||||
:channel-type="channelType"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
|
||||
@@ -92,7 +92,6 @@ const setSignature = () => {
|
||||
|
||||
const toggleMessageSignature = () => {
|
||||
setSignatureFlagForInbox(props.channelType, !sendWithSignature.value);
|
||||
setSignature();
|
||||
};
|
||||
|
||||
// Added this watch to dynamically set signature on target inbox change.
|
||||
|
||||
+14
-8
@@ -199,16 +199,20 @@ const handleInboxAction = ({ value, action, ...rest }) => {
|
||||
state.attachedFiles = [];
|
||||
};
|
||||
|
||||
const removeTargetInbox = value => {
|
||||
v$.value.$reset();
|
||||
// Remove the signature from message content
|
||||
// Based on the Advance Editor (used in isEmailOrWebWidget) and Plain editor(all other inboxes except WhatsApp)
|
||||
if (props.sendWithSignature) {
|
||||
const signatureToRemove = inboxTypes.value.isEmailOrWebWidget
|
||||
? props.messageSignature
|
||||
: extractTextFromMarkdown(props.messageSignature);
|
||||
const removeSignatureFromMessage = () => {
|
||||
// Always remove the signature from message content when inbox/contact is removed
|
||||
// to ensure no leftover signature content remains
|
||||
const signatureToRemove = inboxTypes.value.isEmailOrWebWidget
|
||||
? props.messageSignature
|
||||
: extractTextFromMarkdown(props.messageSignature);
|
||||
if (signatureToRemove) {
|
||||
state.message = removeSignature(state.message, signatureToRemove);
|
||||
}
|
||||
};
|
||||
|
||||
const removeTargetInbox = value => {
|
||||
v$.value.$reset();
|
||||
removeSignatureFromMessage();
|
||||
emit('updateTargetInbox', value);
|
||||
state.attachedFiles = [];
|
||||
};
|
||||
@@ -216,6 +220,7 @@ const removeTargetInbox = value => {
|
||||
const clearSelectedContact = () => {
|
||||
emit('clearSelectedContact');
|
||||
state.attachedFiles = [];
|
||||
removeSignatureFromMessage();
|
||||
};
|
||||
|
||||
const onClickInsertEmoji = emoji => {
|
||||
@@ -354,6 +359,7 @@ const shouldShowMessageEditor = computed(() => {
|
||||
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
|
||||
:has-errors="validationStates.isMessageInvalid"
|
||||
:has-attachments="state.attachedFiles.length > 0"
|
||||
:channel-type="inboxChannelType"
|
||||
/>
|
||||
|
||||
<AttachmentPreviews
|
||||
|
||||
@@ -17,6 +17,7 @@ const props = defineProps({
|
||||
hasAttachments: { type: Boolean, default: false },
|
||||
sendWithSignature: { type: Boolean, default: false },
|
||||
messageSignature: { type: String, default: '' },
|
||||
channelType: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -90,6 +91,10 @@ const replaceText = async message => {
|
||||
"
|
||||
enable-variables
|
||||
:show-character-count="false"
|
||||
:signature="messageSignature"
|
||||
allow-signature
|
||||
:send-with-signature="sendWithSignature"
|
||||
:channel-type="channelType"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -302,7 +302,16 @@ function isBodyEmpty(content) {
|
||||
}
|
||||
|
||||
function handleEmptyBodyWithSignature() {
|
||||
const { schema, tr } = state;
|
||||
const { schema, tr, doc } = state;
|
||||
|
||||
const isEmptyParagraph = node =>
|
||||
node && node.type === schema.nodes.paragraph && node.content.size === 0;
|
||||
|
||||
// Check if empty paragraph already exists to prevent duplicates when toggling signatures
|
||||
if (isEmptyParagraph(doc.firstChild)) {
|
||||
focusEditorInputField('start');
|
||||
return;
|
||||
}
|
||||
|
||||
// create a paragraph node and
|
||||
// start a transaction to append it at the end
|
||||
|
||||
@@ -228,14 +228,17 @@ class Conversation < ApplicationRecord
|
||||
def determine_conversation_status
|
||||
self.status = :resolved and return if contact.blocked?
|
||||
|
||||
# Message template hooks aren't executed for conversations from campaigns
|
||||
# So making these conversations open for agent visibility
|
||||
return if campaign.present?
|
||||
return handle_campaign_status if campaign.present?
|
||||
|
||||
# TODO: make this an inbox config instead of assuming bot conversations should start as pending
|
||||
self.status = :pending if inbox.active_bot?
|
||||
end
|
||||
|
||||
def handle_campaign_status
|
||||
# If campaign has no sender (bot-initiated) and inbox has active bot, let bot handle it
|
||||
self.status = :pending if campaign.sender_id.nil? && inbox.active_bot?
|
||||
end
|
||||
|
||||
def notify_conversation_creation
|
||||
dispatcher_dispatch(CONVERSATION_CREATED)
|
||||
end
|
||||
|
||||
@@ -39,7 +39,14 @@ class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
|
||||
end
|
||||
|
||||
def format_message(message)
|
||||
sender = message.message_type == 'incoming' ? 'User' : 'Support agent'
|
||||
sender = case message.sender_type
|
||||
when 'User'
|
||||
'Support Agent'
|
||||
when 'Contact'
|
||||
'User'
|
||||
else
|
||||
'Bot'
|
||||
end
|
||||
sender = "[Private Note] #{sender}" if message.private?
|
||||
"#{sender}: #{message.content}\n"
|
||||
end
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
"dependencies": {
|
||||
"@breezystack/lamejs": "^1.2.7",
|
||||
"@chatwoot/ninja-keys": "1.2.3",
|
||||
"@chatwoot/prosemirror-schema": "1.2.1",
|
||||
"@chatwoot/prosemirror-schema": "1.2.3",
|
||||
"@chatwoot/utils": "^0.0.51",
|
||||
"@formkit/core": "^1.6.7",
|
||||
"@formkit/vue": "^1.6.7",
|
||||
|
||||
Generated
+5
-5
@@ -20,8 +20,8 @@ importers:
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3
|
||||
'@chatwoot/prosemirror-schema':
|
||||
specifier: 1.2.1
|
||||
version: 1.2.1
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3
|
||||
'@chatwoot/utils':
|
||||
specifier: ^0.0.51
|
||||
version: 0.0.51
|
||||
@@ -406,8 +406,8 @@ packages:
|
||||
'@chatwoot/ninja-keys@1.2.3':
|
||||
resolution: {integrity: sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==}
|
||||
|
||||
'@chatwoot/prosemirror-schema@1.2.1':
|
||||
resolution: {integrity: sha512-UbiEvG5tgi1d0lMbkaqxgTh7vHfywEYKLQo1sxqp4Q7aLZh4QFtbLzJ2zyBtu4Nhipe+guFfEJdic7i43MP/XQ==}
|
||||
'@chatwoot/prosemirror-schema@1.2.3':
|
||||
resolution: {integrity: sha512-q/EfirVK9jt8FJAx3Gf6y3LoVadmYVLknbYvPrkUe81WO0f2mkZ/kY2UQgpUISVvOGEkCH4bkfYMp5UQ+Buz3g==}
|
||||
|
||||
'@chatwoot/utils@0.0.51':
|
||||
resolution: {integrity: sha512-WlEmWfOTzR7YZRUWzn5Wpm15/BRudpwqoNckph8TohyDbiim1CP4UZGa+qjajxTbNGLLhtKlm0Xl+X16+5Wceg==}
|
||||
@@ -4768,7 +4768,7 @@ snapshots:
|
||||
hotkeys-js: 3.8.7
|
||||
lit: 2.2.6
|
||||
|
||||
'@chatwoot/prosemirror-schema@1.2.1':
|
||||
'@chatwoot/prosemirror-schema@1.2.3':
|
||||
dependencies:
|
||||
markdown-it-sup: 2.0.0
|
||||
prosemirror-commands: 1.6.0
|
||||
|
||||
@@ -27,6 +27,13 @@ FactoryBot.define do
|
||||
end
|
||||
end
|
||||
|
||||
trait :bot_message do
|
||||
message_type { 'outgoing' }
|
||||
after(:build) do |message|
|
||||
message.sender = nil
|
||||
end
|
||||
end
|
||||
|
||||
after(:build) do |message|
|
||||
message.sender ||= message.outgoing? ? create(:user, account: message.account) : create(:contact, account: message.account)
|
||||
message.inbox ||= message.conversation&.inbox || create(:inbox, account: message.account)
|
||||
|
||||
@@ -576,9 +576,38 @@ RSpec.describe Conversation do
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
|
||||
it 'returns conversation as open if campaign is present' do
|
||||
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: create(:campaign))
|
||||
expect(conversation.status).to eq('open')
|
||||
context 'with campaigns' do
|
||||
let(:user) { create(:user, account: bot_inbox.inbox.account) }
|
||||
|
||||
it 'returns conversation as open if campaign has a sender' do
|
||||
campaign = create(:campaign, inbox: bot_inbox.inbox, account: bot_inbox.inbox.account, sender: user)
|
||||
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: campaign)
|
||||
expect(conversation.status).to eq('open')
|
||||
end
|
||||
|
||||
it 'returns conversation as pending if campaign has no sender (bot-initiated) and bot is active' do
|
||||
campaign = create(:campaign, inbox: bot_inbox.inbox, account: bot_inbox.inbox.account, sender: nil)
|
||||
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: campaign)
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with campaigns in inbox without bot' do
|
||||
let(:account) { create(:account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:user) { create(:user, account: account) }
|
||||
|
||||
it 'returns conversation as open if campaign has no sender but no bot is active' do
|
||||
campaign = create(:campaign, inbox: inbox, account: account, sender: nil)
|
||||
conversation = create(:conversation, inbox: inbox, campaign: campaign)
|
||||
expect(conversation.status).to eq('open')
|
||||
end
|
||||
|
||||
it 'returns conversation as open if campaign has a sender' do
|
||||
campaign = create(:campaign, inbox: inbox, account: account, sender: user)
|
||||
conversation = create(:conversation, inbox: inbox, campaign: campaign)
|
||||
expect(conversation.status).to eq('open')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ RSpec.describe LlmFormatter::ConversationLlmFormatter do
|
||||
content: 'Hello, I need help'
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
:bot_message,
|
||||
conversation: conversation,
|
||||
message_type: 'outgoing',
|
||||
content: 'Thanks for reaching out, an agent will reach out to you soon'
|
||||
)
|
||||
|
||||
create(
|
||||
:message,
|
||||
conversation: conversation,
|
||||
@@ -40,7 +48,8 @@ RSpec.describe LlmFormatter::ConversationLlmFormatter do
|
||||
"Channel: #{conversation.inbox.channel.name}",
|
||||
'Message History:',
|
||||
'User: Hello, I need help',
|
||||
'Support agent: How can I assist you today?',
|
||||
'Bot: Thanks for reaching out, an agent will reach out to you soon',
|
||||
'Support Agent: How can I assist you today?',
|
||||
''
|
||||
].join("\n")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user