From 0c2ab7f5e7b349b2aca1529ca14c7c9b7554f257 Mon Sep 17 00:00:00 2001 From: Pranav Date: Wed, 27 Aug 2025 21:40:28 -0700 Subject: [PATCH] feat(ee): Setup advanced, performant message search (#12193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now support searching within the actual message content, email subject lines, and audio transcriptions. This enables a faster, more accurate search experience going forward. Unlike the standard message search, which is limited to the last 3 months, this search has no time restrictions. The search engine also accounts for small variations in queries. Minor spelling mistakes, such as searching for slck instead of Slack, will still return the correct results. It also ignores differences in accents and diacritics, so searching for Deja vu will match content containing Déjà vu. We can also refine searches in the future by criteria such as: - Searching within a specific inbox - Filtering by sender or recipient - Limiting to messages sent by an agent Fixes https://github.com/chatwoot/chatwoot/issues/11656 Fixes https://github.com/chatwoot/chatwoot/issues/10669 Fixes https://github.com/chatwoot/chatwoot/issues/5910 --- Rake tasks to reindex all the messages. ```sh bundle exec rake search:all ``` Rake task to reindex messages from one account only ```sh bundle exec rake search:account ACCOUNT_ID=1 ``` --- Gemfile | 4 ++ Gemfile.lock | 12 +++++ Rakefile | 4 ++ .../search/components/MessageContent.vue | 32 ++++++++--- .../components/SearchResultMessagesList.vue | 2 +- app/models/message.rb | 23 +++++++- app/services/search_service.rb | 10 ++++ .../v1/accounts/search/_message.json.jbuilder | 13 +---- config/features.yml | 5 ++ config/initializers/searchkick.rb | 14 +++++ .../app/services/enterprise/search_service.rb | 15 ++++++ .../messages/audio_transcription_service.rb | 4 ++ .../app/services/messages/reindex_service.rb | 15 ++++++ enterprise/lib/tasks.rb | 4 ++ enterprise/lib/tasks/search.rake | 49 +++++++++++++++++ lib/chatwoot_app.rb | 4 ++ spec/models/message_spec.rb | 53 +++++++++++++++++++ 17 files changed, 242 insertions(+), 21 deletions(-) create mode 100644 config/initializers/searchkick.rb create mode 100644 enterprise/app/services/enterprise/search_service.rb create mode 100644 enterprise/app/services/messages/reindex_service.rb create mode 100644 enterprise/lib/tasks.rb create mode 100644 enterprise/lib/tasks/search.rake diff --git a/Gemfile b/Gemfile index 615267a77..1148251af 100644 --- a/Gemfile +++ b/Gemfile @@ -62,6 +62,10 @@ gem 'redis-namespace' # super fast record imports in bulk gem 'activerecord-import' +gem 'searchkick' +gem 'opensearch-ruby' +gem 'faraday_middleware-aws-sigv4' + ##--- gems for server & infra configuration ---## gem 'dotenv-rails', '>= 3.0.0' gem 'foreman' diff --git a/Gemfile.lock b/Gemfile.lock index 8fa38a31e..26814db59 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,6 +299,9 @@ GEM net-http-persistent (~> 4.0) faraday-retry (2.2.1) faraday (~> 2.0) + faraday_middleware-aws-sigv4 (1.0.1) + aws-sigv4 (~> 1.0) + faraday (>= 2.0, < 3) fast-mcp (1.5.0) addressable (~> 2.8) base64 @@ -601,6 +604,9 @@ GEM omniauth-rails_csrf_protection (1.0.2) actionpack (>= 4.2) omniauth (~> 2.0) + opensearch-ruby (3.4.0) + faraday (>= 1.0, < 3) + multi_json (>= 1.0) openssl (3.2.0) orm_adapter (0.5.0) os (1.1.4) @@ -802,6 +808,9 @@ GEM parser scss_lint (0.60.0) sass (~> 3.5, >= 3.5.5) + searchkick (5.5.2) + activemodel (>= 7.1) + hashie securerandom (0.4.1) seed_dump (3.3.1) activerecord (>= 4) @@ -996,6 +1005,7 @@ DEPENDENCIES facebook-messenger factory_bot_rails (>= 6.4.3) faker + faraday_middleware-aws-sigv4 fcm flag_shih_tzu foreman @@ -1036,6 +1046,7 @@ DEPENDENCIES omniauth-google-oauth2 (>= 1.1.3) omniauth-oauth2 omniauth-rails_csrf_protection (~> 1.0, >= 1.0.2) + opensearch-ruby pg pg_search pgvector @@ -1064,6 +1075,7 @@ DEPENDENCIES ruby_llm-schema scout_apm scss_lint + searchkick seed_dump sentry-rails (>= 5.19.0) sentry-ruby diff --git a/Rakefile b/Rakefile index e85f91391..591d2c4c6 100644 --- a/Rakefile +++ b/Rakefile @@ -4,3 +4,7 @@ require_relative 'config/application' Rails.application.load_tasks + +# Load Enterprise Edition rake tasks if they exist +enterprise_tasks_path = Rails.root.join('enterprise/lib/tasks.rb').to_s +require enterprise_tasks_path if File.exist?(enterprise_tasks_path) diff --git a/app/javascript/dashboard/modules/search/components/MessageContent.vue b/app/javascript/dashboard/modules/search/components/MessageContent.vue index fef35ac89..9ec425919 100644 --- a/app/javascript/dashboard/modules/search/components/MessageContent.vue +++ b/app/javascript/dashboard/modules/search/components/MessageContent.vue @@ -1,5 +1,5 @@