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
|
||||
# snoozed_until :datetime
|
||||
# status :integer default("open"), not null
|
||||
# summary :text
|
||||
# summary_generated_at :datetime
|
||||
# uuid :uuid not null
|
||||
# waiting_since :datetime
|
||||
# created_at :datetime not null
|
||||
@@ -57,6 +59,7 @@ class Conversation < ApplicationRecord
|
||||
include UrlHelper
|
||||
include SortHandler
|
||||
include ConversationMuteHelpers
|
||||
include ConversationPresentationHelper
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
@@ -122,6 +125,14 @@ class Conversation < ApplicationRecord
|
||||
last_message_in_messaging_window?(messaging_window)
|
||||
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
|
||||
messages&.incoming&.last
|
||||
end
|
||||
@@ -173,18 +184,6 @@ class Conversation < ApplicationRecord
|
||||
messages.incoming.unread_since(agent_last_seen_at)
|
||||
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?
|
||||
return false unless saved_change_to_assignee_id?
|
||||
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_activity_at conversation.last_activity_at.to_i
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -451,6 +451,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_14_054138) do
|
||||
t.integer "priority"
|
||||
t.bigint "sla_policy_id"
|
||||
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", "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"
|
||||
|
||||
@@ -25,4 +25,4 @@ Reply in the user's language, as a markdown of the following format.
|
||||
|
||||
**Action Items**
|
||||
|
||||
**Follow-up Items**
|
||||
**Follow-up Items**
|
||||
|
||||
@@ -8,6 +8,19 @@ class ChatwootMarkdownRenderer
|
||||
render_as_html_safe(html)
|
||||
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
|
||||
markdown_renderer = CustomMarkdownRenderer.new
|
||||
doc = CommonMarker.render_doc(@content, :DEFAULT)
|
||||
@@ -18,6 +31,10 @@ class ChatwootMarkdownRenderer
|
||||
|
||||
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)
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
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}")
|
||||
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)
|
||||
conversation = find_conversation
|
||||
messages = init_messages_body(in_array_format)
|
||||
|
||||
@@ -24,6 +24,16 @@ class Integrations::OpenaiBaseService
|
||||
|
||||
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
|
||||
event['name']
|
||||
end
|
||||
|
||||
@@ -7,38 +7,66 @@ RSpec.describe ChatwootMarkdownRenderer do
|
||||
let(:markdown_renderer) { instance_double(CustomMarkdownRenderer) }
|
||||
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
|
||||
let(:rendered_content) { renderer.render_article }
|
||||
|
||||
it 'renders the markdown content to html' do
|
||||
expect(rendered_content.to_s).to eq(html_content)
|
||||
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
|
||||
|
||||
it 'returns an html safe string' do
|
||||
expect(rendered_content).to be_html_safe
|
||||
context 'when rendering a article' do
|
||||
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
|
||||
|
||||
describe '#render_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)
|
||||
describe '#remove_empty_headers' do
|
||||
it 'removes headers with no subheaders or content' do
|
||||
payload = "# Empty Header\n\n# 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("# Header 1\n\nSome content\n\n# Header 2\n\nSome more content")
|
||||
end
|
||||
|
||||
it 'renders the markdown message to html' do
|
||||
expect(rendered_message.to_s).to eq(message_html_content)
|
||||
it 'does not remove headers with subheaders' do
|
||||
payload = "# Header 1\n\n## Subheader\n\nSome content"
|
||||
renderer = described_class.new(payload)
|
||||
expect(renderer.remove_empty_headers).to eq(payload)
|
||||
end
|
||||
|
||||
it 'returns an html safe string' do
|
||||
expect(rendered_message).to be_html_safe
|
||||
it 'does not remove headers with content' do
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user