Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97987dcda2 | ||
|
|
bda0234618 | ||
|
|
1083d0e974 | ||
|
|
9f879c440e | ||
|
|
811dc077fc | ||
|
|
445d948af0 | ||
|
|
d99eb145f2 | ||
|
|
76820e2834 | ||
|
|
c862f6d419 | ||
|
|
16b7cabcc8 | ||
|
|
95a547d717 | ||
|
|
e387f54130 | ||
|
|
1ccb4e1cc2 | ||
|
|
23e840dabe | ||
|
|
4b30abf44c | ||
|
|
b65a2f7ebd | ||
|
|
babe1028e2 | ||
|
|
8b27123f8c | ||
|
|
0b37e11ebf |
@@ -0,0 +1,15 @@
|
|||||||
|
module ConversationPresentationHelper
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def push_event_data
|
||||||
|
Conversations::EventDataPresenter.new(self).push_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def lock_event_data
|
||||||
|
Conversations::EventDataPresenter.new(self).lock_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def webhook_data
|
||||||
|
Conversations::EventDataPresenter.new(self).push_data
|
||||||
|
end
|
||||||
|
end
|
||||||
+11
-12
@@ -14,6 +14,8 @@
|
|||||||
# priority :integer
|
# priority :integer
|
||||||
# snoozed_until :datetime
|
# snoozed_until :datetime
|
||||||
# status :integer default("open"), not null
|
# status :integer default("open"), not null
|
||||||
|
# summary :text
|
||||||
|
# summary_generated_at :datetime
|
||||||
# uuid :uuid not null
|
# uuid :uuid not null
|
||||||
# waiting_since :datetime
|
# waiting_since :datetime
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
@@ -57,6 +59,7 @@ class Conversation < ApplicationRecord
|
|||||||
include UrlHelper
|
include UrlHelper
|
||||||
include SortHandler
|
include SortHandler
|
||||||
include ConversationMuteHelpers
|
include ConversationMuteHelpers
|
||||||
|
include ConversationPresentationHelper
|
||||||
|
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :inbox_id, presence: true
|
validates :inbox_id, presence: true
|
||||||
@@ -122,6 +125,14 @@ class Conversation < ApplicationRecord
|
|||||||
last_message_in_messaging_window?(messaging_window)
|
last_message_in_messaging_window?(messaging_window)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rendered_summary
|
||||||
|
return nil if summary.nil?
|
||||||
|
|
||||||
|
render_engine = ChatwootMarkdownRenderer.new(summary)
|
||||||
|
render_engine.remove_empty_headers
|
||||||
|
render_engine.render_message
|
||||||
|
end
|
||||||
|
|
||||||
def last_incoming_message
|
def last_incoming_message
|
||||||
messages&.incoming&.last
|
messages&.incoming&.last
|
||||||
end
|
end
|
||||||
@@ -173,18 +184,6 @@ class Conversation < ApplicationRecord
|
|||||||
messages.incoming.unread_since(agent_last_seen_at)
|
messages.incoming.unread_since(agent_last_seen_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_event_data
|
|
||||||
Conversations::EventDataPresenter.new(self).push_data
|
|
||||||
end
|
|
||||||
|
|
||||||
def lock_event_data
|
|
||||||
Conversations::EventDataPresenter.new(self).lock_data
|
|
||||||
end
|
|
||||||
|
|
||||||
def webhook_data
|
|
||||||
Conversations::EventDataPresenter.new(self).push_data
|
|
||||||
end
|
|
||||||
|
|
||||||
def notifiable_assignee_change?
|
def notifiable_assignee_change?
|
||||||
return false unless saved_change_to_assignee_id?
|
return false unless saved_change_to_assignee_id?
|
||||||
return false if assignee_id.blank?
|
return false if assignee_id.blank?
|
||||||
|
|||||||
@@ -45,4 +45,6 @@ json.unread_count conversation.unread_incoming_messages.count
|
|||||||
json.last_non_activity_message conversation.messages.non_activity_messages.first.try(:push_event_data)
|
json.last_non_activity_message conversation.messages.non_activity_messages.first.try(:push_event_data)
|
||||||
json.last_activity_at conversation.last_activity_at.to_i
|
json.last_activity_at conversation.last_activity_at.to_i
|
||||||
json.priority conversation.priority
|
json.priority conversation.priority
|
||||||
|
json.summary conversation.rendered_summary
|
||||||
|
json.summary_generated_at conversation.summary_generated_at.to_i
|
||||||
json.waiting_since conversation.waiting_since.to_i.to_i
|
json.waiting_since conversation.waiting_since.to_i.to_i
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
class AddSummaryFieldsToConversations < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :conversations, :summary, :text
|
||||||
|
add_column :conversations, :summary_generated_at, :datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
+3
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_07_14_054138) do
|
ActiveRecord::Schema[7.0].define(version: 2023_07_17_075819) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
@@ -451,6 +451,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_14_054138) do
|
|||||||
t.integer "priority"
|
t.integer "priority"
|
||||||
t.bigint "sla_policy_id"
|
t.bigint "sla_policy_id"
|
||||||
t.datetime "waiting_since"
|
t.datetime "waiting_since"
|
||||||
|
t.text "summary"
|
||||||
|
t.datetime "summary_generated_at"
|
||||||
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
||||||
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
|
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
|
||||||
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
|
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ Reply in the user's language, as a markdown of the following format.
|
|||||||
|
|
||||||
**Action Items**
|
**Action Items**
|
||||||
|
|
||||||
**Follow-up Items**
|
**Follow-up Items**
|
||||||
|
|||||||
@@ -8,6 +8,19 @@ class ChatwootMarkdownRenderer
|
|||||||
render_as_html_safe(html)
|
render_as_html_safe(html)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_empty_headers
|
||||||
|
doc = CommonMarker.render_doc(@content, :DEFAULT)
|
||||||
|
|
||||||
|
doc.walk do |node|
|
||||||
|
if node.type == :header
|
||||||
|
next_node = node.next
|
||||||
|
node.delete if node_removable?(node, next_node)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@content = doc.to_commonmark.strip
|
||||||
|
end
|
||||||
|
|
||||||
def render_article
|
def render_article
|
||||||
markdown_renderer = CustomMarkdownRenderer.new
|
markdown_renderer = CustomMarkdownRenderer.new
|
||||||
doc = CommonMarker.render_doc(@content, :DEFAULT)
|
doc = CommonMarker.render_doc(@content, :DEFAULT)
|
||||||
@@ -18,6 +31,10 @@ class ChatwootMarkdownRenderer
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def node_removable?(node, next_node)
|
||||||
|
next_node.nil? || (next_node.type == :header && next_node.header_level <= node.header_level)
|
||||||
|
end
|
||||||
|
|
||||||
def render_as_html_safe(html)
|
def render_as_html_safe(html)
|
||||||
# rubocop:disable Rails/OutputSafety
|
# rubocop:disable Rails/OutputSafety
|
||||||
html.html_safe
|
html.html_safe
|
||||||
|
|||||||
@@ -69,16 +69,6 @@ class Integrations::Openai::ProcessorService < Integrations::OpenaiBaseService
|
|||||||
build_api_call_body("#{AGENT_INSTRUCTION} Please simplify the following response. #{LANGUAGE_INSTRUCTION}")
|
build_api_call_body("#{AGENT_INSTRUCTION} Please simplify the following response. #{LANGUAGE_INSTRUCTION}")
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_api_call_body(system_content, user_content = event['data']['content'])
|
|
||||||
{
|
|
||||||
model: GPT_MODEL,
|
|
||||||
messages: [
|
|
||||||
{ role: 'system', content: system_content },
|
|
||||||
{ role: 'user', content: user_content }
|
|
||||||
]
|
|
||||||
}.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def conversation_messages(in_array_format: false)
|
def conversation_messages(in_array_format: false)
|
||||||
conversation = find_conversation
|
conversation = find_conversation
|
||||||
messages = init_messages_body(in_array_format)
|
messages = init_messages_body(in_array_format)
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ class Integrations::OpenaiBaseService
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def build_api_call_body(system_content, user_content = event['data']['content'])
|
||||||
|
{
|
||||||
|
model: GPT_MODEL,
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: system_content },
|
||||||
|
{ role: 'user', content: user_content }
|
||||||
|
]
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
def event_name
|
def event_name
|
||||||
event['name']
|
event['name']
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,38 +7,66 @@ RSpec.describe ChatwootMarkdownRenderer do
|
|||||||
let(:markdown_renderer) { instance_double(CustomMarkdownRenderer) }
|
let(:markdown_renderer) { instance_double(CustomMarkdownRenderer) }
|
||||||
let(:html_content) { '<p>This is a <em>test</em> content with <sup>markdown</sup></p>' }
|
let(:html_content) { '<p>This is a <em>test</em> content with <sup>markdown</sup></p>' }
|
||||||
|
|
||||||
before do
|
|
||||||
allow(CommonMarker).to receive(:render_doc).with(markdown_content, :DEFAULT).and_return(doc)
|
|
||||||
allow(CustomMarkdownRenderer).to receive(:new).and_return(markdown_renderer)
|
|
||||||
allow(markdown_renderer).to receive(:render).with(doc).and_return(html_content)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#render_article' do
|
describe '#render_article' do
|
||||||
let(:rendered_content) { renderer.render_article }
|
before do
|
||||||
|
allow(CommonMarker).to receive(:render_doc).with(markdown_content, :DEFAULT).and_return(doc)
|
||||||
it 'renders the markdown content to html' do
|
allow(CustomMarkdownRenderer).to receive(:new).and_return(markdown_renderer)
|
||||||
expect(rendered_content.to_s).to eq(html_content)
|
allow(markdown_renderer).to receive(:render).with(doc).and_return(html_content)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an html safe string' do
|
context 'when rendering a article' do
|
||||||
expect(rendered_content).to be_html_safe
|
let(:rendered_content) { renderer.render_article }
|
||||||
|
|
||||||
|
it 'renders the markdown content to html' do
|
||||||
|
expect(rendered_content.to_s).to eq(html_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an html safe string' do
|
||||||
|
expect(rendered_content).to be_html_safe
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when rendering a message' do
|
||||||
|
let(:message_html_content) { '<p>This is a <em>test</em> content with ^markdown^</p>' }
|
||||||
|
let(:rendered_message) { renderer.render_message }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(CommonMarker).to receive(:render_html).with(markdown_content).and_return(message_html_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders the markdown message to html' do
|
||||||
|
expect(rendered_message.to_s).to eq(message_html_content)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an html safe string' do
|
||||||
|
expect(rendered_message).to be_html_safe
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#render_message' do
|
describe '#remove_empty_headers' do
|
||||||
let(:message_html_content) { '<p>This is a <em>test</em> content with ^markdown^</p>' }
|
it 'removes headers with no subheaders or content' do
|
||||||
let(:rendered_message) { renderer.render_message }
|
payload = "# Empty Header\n\n# Header 1\n\nSome content\n\n# Header 2\n\nSome more content"
|
||||||
|
renderer = described_class.new(payload)
|
||||||
before do
|
expect(renderer.remove_empty_headers).to eq("# Header 1\n\nSome content\n\n# Header 2\n\nSome more content")
|
||||||
allow(CommonMarker).to receive(:render_html).with(markdown_content).and_return(message_html_content)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the markdown message to html' do
|
it 'does not remove headers with subheaders' do
|
||||||
expect(rendered_message.to_s).to eq(message_html_content)
|
payload = "# Header 1\n\n## Subheader\n\nSome content"
|
||||||
|
renderer = described_class.new(payload)
|
||||||
|
expect(renderer.remove_empty_headers).to eq(payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an html safe string' do
|
it 'does not remove headers with content' do
|
||||||
expect(rendered_message).to be_html_safe
|
payload = "# Header 1\n\nSome content\n\n# Header 2\n\nSome more content"
|
||||||
|
renderer = described_class.new(payload)
|
||||||
|
expect(renderer.remove_empty_headers).to eq(payload)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not remove headers with subheaders and content' do
|
||||||
|
payload = "# Header 1\n\n## Subheader\n\nSome content\n\n# Header 2\n\nSome more content"
|
||||||
|
renderer = described_class.new(payload)
|
||||||
|
expect(renderer.remove_empty_headers).to eq(payload)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user