Restructure omni services and add Chatwoot research snapshot

This commit is contained in:
Ruslan Bakiev
2026-02-21 11:11:27 +07:00
parent edea7a0034
commit b73babbbf6
7732 changed files with 978203 additions and 32 deletions

View File

@@ -0,0 +1,22 @@
class LlmFormatter::ArticleLlmFormatter
attr_reader :article
def initialize(article)
@article = article
end
def format(*)
<<~TEXT
Title: #{article.title}
ID: #{article.id}
Status: #{article.status}
Category: #{article.category&.name || 'Uncategorized'}
Author: #{article.author&.name || 'Unknown'}
Views: #{article.views}
Created At: #{article.created_at}
Updated At: #{article.updated_at}
Content:
#{article.content}
TEXT
end
end

View File

@@ -0,0 +1,35 @@
class LlmFormatter::ContactLlmFormatter < LlmFormatter::DefaultLlmFormatter
def format(*)
sections = []
sections << "Contact ID: ##{@record.id}"
sections << 'Contact Attributes:'
sections << build_attributes
sections << 'Contact Notes:'
sections << if @record.notes.any?
build_notes
else
'No notes for this contact'
end
sections.join("\n")
end
private
def build_notes
@record.notes.all.map { |note| " - #{note.content}" }.join("\n")
end
def build_attributes
attributes = []
attributes << "Name: #{@record.name}"
attributes << "Email: #{@record.email}"
attributes << "Phone: #{@record.phone_number}"
attributes << "Location: #{@record.location}"
attributes << "Country Code: #{@record.country_code}"
@record.account.custom_attribute_definitions.with_attribute_model('contact_attribute').each do |attribute|
attributes << "#{attribute.attribute_display_name}: #{@record.custom_attributes[attribute.attribute_key]}"
end
attributes.join("\n")
end
end

View File

@@ -0,0 +1,86 @@
class LlmFormatter::ConversationLlmFormatter < LlmFormatter::DefaultLlmFormatter
def format(config = {})
sections = []
sections << "Conversation ID: ##{@record.display_id}"
sections << "Channel: #{@record.inbox.channel.name}"
sections << 'Message History:'
sections << if @record.messages.any?
build_messages(config)
else
'No messages in this conversation'
end
sections << "Contact Details: #{@record.contact.to_llm_text}" if config[:include_contact_details]
attributes = build_attributes
if attributes.present?
sections << 'Conversation Attributes:'
sections << attributes
end
sections.join("\n")
end
private
def build_messages(config = {})
return "No messages in this conversation\n" if @record.messages.empty?
messages = @record.messages.where.not(message_type: [:activity, :template])
if config[:token_limit]
build_limited_messages(messages, config)
else
build_all_messages(messages, config)
end
end
def build_all_messages(messages, config)
message_text = ''
messages.order(created_at: :asc).each do |message|
# Skip private messages unless explicitly included in config
next if message.private? && !config[:include_private_messages]
message_text << format_message(message)
end
message_text
end
def build_limited_messages(messages, config)
selected = []
character_count = 0
messages.reorder(created_at: :desc).each do |message|
# Skip private messages unless explicitly included in config
next if message.private? && !config[:include_private_messages]
formatted = format_message(message)
break if character_count + formatted.length > config[:token_limit]
selected.prepend(formatted)
character_count += formatted.length
end
selected.join
end
def format_message(message)
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_for_llm}\n"
end
def build_attributes
attributes = @record.account.custom_attribute_definitions.with_attribute_model('conversation_attribute').map do |attribute|
"#{attribute.attribute_display_name}: #{@record.custom_attributes[attribute.attribute_key]}"
end
attributes.join("\n")
end
end

View File

@@ -0,0 +1,9 @@
class LlmFormatter::DefaultLlmFormatter
def initialize(record)
@record = record
end
def format(*)
# override this
end
end

View File

@@ -0,0 +1,20 @@
class LlmFormatter::LlmTextFormatterService
def initialize(record)
@record = record
end
def format(config = {})
formatter_class = find_formatter
formatter_class.new(@record).format(config)
end
private
def find_formatter
formatter_name = "LlmFormatter::#{@record.class.name}LlmFormatter"
formatter_class = formatter_name.safe_constantize
raise FormatterNotFoundError, "No formatter found for #{@record.class.name}" unless formatter_class
formatter_class
end
end