+
+
+ {{ $t('INBOX_MGMT.FINISH.WHATSAPP_QR_INSTRUCTION') }}
+
+
+
![WhatsApp QR Code]()
+
+
+
+
+ {{ $t('INBOX_MGMT.FINISH.MESSENGER_QR_INSTRUCTION') }}
+
+
+
![Messenger QR Code]()
+
+
+
+
+ {{ $t('INBOX_MGMT.FINISH.TELEGRAM_QR_INSTRUCTION') }}
+
+
+
+
![Telegram QR Code]()
+
+
+
{ check_authorization(AgentCapacityPolicy) }
+ before_action :fetch_policy
+ before_action :fetch_inbox, only: [:create]
+ before_action :fetch_inbox_limit, only: [:update, :destroy]
+ before_action :validate_no_duplicate, only: [:create]
+
+ def create
+ @inbox_limit = @agent_capacity_policy.inbox_capacity_limits.create!(
+ inbox: @inbox,
+ conversation_limit: permitted_params[:conversation_limit]
+ )
+ end
+
+ def update
+ @inbox_limit.update!(conversation_limit: permitted_params[:conversation_limit])
+ end
+
+ def destroy
+ @inbox_limit.destroy!
+ head :no_content
+ end
+
+ private
+
+ def fetch_policy
+ @agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:agent_capacity_policy_id])
+ end
+
+ def fetch_inbox
+ @inbox = Current.account.inboxes.find(permitted_params[:inbox_id])
+ end
+
+ def fetch_inbox_limit
+ @inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find(params[:id])
+ end
+
+ def validate_no_duplicate
+ return unless @agent_capacity_policy.inbox_capacity_limits.exists?(inbox: @inbox)
+
+ render_could_not_create_error(I18n.t('agent_capacity_policy.inbox_already_assigned'))
+ end
+
+ def permitted_params
+ params.permit(:inbox_id, :conversation_limit)
+ end
+end
diff --git a/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb
new file mode 100644
index 000000000..a49b4f00f
--- /dev/null
+++ b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb
@@ -0,0 +1,35 @@
+class Api::V1::Accounts::AgentCapacityPolicies::UsersController < Api::V1::Accounts::EnterpriseAccountsController
+ before_action -> { check_authorization(AgentCapacityPolicy) }
+ before_action :fetch_policy
+ before_action :fetch_user, only: [:destroy]
+
+ def index
+ @users = Current.account.users.joins(:account_users)
+ .where(account_users: { agent_capacity_policy_id: @agent_capacity_policy.id })
+ end
+
+ def create
+ @account_user = Current.account.account_users.find_by!(user_id: permitted_params[:user_id])
+ @account_user.update!(agent_capacity_policy: @agent_capacity_policy)
+ @user = @account_user.user
+ end
+
+ def destroy
+ @account_user.update!(agent_capacity_policy: nil)
+ head :ok
+ end
+
+ private
+
+ def fetch_policy
+ @agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:agent_capacity_policy_id])
+ end
+
+ def fetch_user
+ @account_user = Current.account.account_users.find_by!(user_id: params[:id])
+ end
+
+ def permitted_params
+ params.permit(:user_id)
+ end
+end
diff --git a/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb
new file mode 100644
index 000000000..d6d166ee5
--- /dev/null
+++ b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb
@@ -0,0 +1,37 @@
+class Api::V1::Accounts::AgentCapacityPoliciesController < Api::V1::Accounts::EnterpriseAccountsController
+ before_action :check_authorization
+ before_action :fetch_policy, only: [:show, :update, :destroy]
+
+ def index
+ @agent_capacity_policies = Current.account.agent_capacity_policies
+ end
+
+ def show; end
+
+ def create
+ @agent_capacity_policy = Current.account.agent_capacity_policies.create!(permitted_params)
+ end
+
+ def update
+ @agent_capacity_policy.update!(permitted_params)
+ end
+
+ def destroy
+ @agent_capacity_policy.destroy!
+ head :ok
+ end
+
+ private
+
+ def permitted_params
+ params.require(:agent_capacity_policy).permit(
+ :name,
+ :description,
+ exclusion_rules: [:overall_capacity, { hours: [], days: [] }]
+ )
+ end
+
+ def fetch_policy
+ @agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:id])
+ end
+end
diff --git a/enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb b/enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb
index 594aa0642..32fa0a7b6 100644
--- a/enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb
+++ b/enterprise/app/controllers/api/v1/accounts/captain/documents_controller.rb
@@ -25,6 +25,8 @@ class Api::V1::Accounts::Captain::DocumentsController < Api::V1::Accounts::BaseC
@document.save!
rescue Captain::Document::LimitExceededError => e
render_could_not_create_error(e.message)
+ rescue ActiveRecord::RecordInvalid => e
+ render_could_not_create_error(e.record.errors.full_messages.join(', '))
end
def destroy
@@ -55,6 +57,6 @@ class Api::V1::Accounts::Captain::DocumentsController < Api::V1::Accounts::BaseC
end
def document_params
- params.require(:document).permit(:name, :external_link, :assistant_id)
+ params.require(:document).permit(:name, :external_link, :assistant_id, :pdf_file)
end
end
diff --git a/enterprise/app/jobs/captain/documents/crawl_job.rb b/enterprise/app/jobs/captain/documents/crawl_job.rb
index 132671385..3fa5b6d56 100644
--- a/enterprise/app/jobs/captain/documents/crawl_job.rb
+++ b/enterprise/app/jobs/captain/documents/crawl_job.rb
@@ -2,7 +2,9 @@ class Captain::Documents::CrawlJob < ApplicationJob
queue_as :low
def perform(document)
- if InstallationConfig.find_by(name: 'CAPTAIN_FIRECRAWL_API_KEY')&.value.present?
+ if document.pdf_document?
+ perform_pdf_processing(document)
+ elsif InstallationConfig.find_by(name: 'CAPTAIN_FIRECRAWL_API_KEY')&.value.present?
perform_firecrawl_crawl(document)
else
perform_simple_crawl(document)
@@ -13,6 +15,14 @@ class Captain::Documents::CrawlJob < ApplicationJob
include Captain::FirecrawlHelper
+ def perform_pdf_processing(document)
+ Captain::Llm::PdfProcessingService.new(document).process
+ document.update!(status: :available)
+ rescue StandardError => e
+ Rails.logger.error I18n.t('captain.documents.pdf_processing_failed', document_id: document.id, error: e.message)
+ raise # Re-raise to let job framework handle retry logic
+ end
+
def perform_simple_crawl(document)
page_links = Captain::Tools::SimplePageCrawlService.new(document.external_link).page_links
diff --git a/enterprise/app/jobs/captain/documents/response_builder_job.rb b/enterprise/app/jobs/captain/documents/response_builder_job.rb
index 21025399f..5dacb416f 100644
--- a/enterprise/app/jobs/captain/documents/response_builder_job.rb
+++ b/enterprise/app/jobs/captain/documents/response_builder_job.rb
@@ -1,17 +1,65 @@
class Captain::Documents::ResponseBuilderJob < ApplicationJob
queue_as :low
- def perform(document)
+ def perform(document, options = {})
reset_previous_responses(document)
- faqs = Captain::Llm::FaqGeneratorService.new(document.content, document.account.locale_english_name).generate
- faqs.each do |faq|
- create_response(faq, document)
- end
+ faqs = generate_faqs(document, options)
+ create_responses_from_faqs(faqs, document)
end
private
+ def generate_faqs(document, options)
+ if should_use_pagination?(document)
+ generate_paginated_faqs(document, options)
+ else
+ generate_standard_faqs(document)
+ end
+ end
+
+ def generate_paginated_faqs(document, options)
+ service = build_paginated_service(document, options)
+ faqs = service.generate
+ store_paginated_metadata(document, service)
+ faqs
+ end
+
+ def generate_standard_faqs(document)
+ Captain::Llm::FaqGeneratorService.new(document.content, document.account.locale_english_name).generate
+ end
+
+ def build_paginated_service(document, options)
+ Captain::Llm::PaginatedFaqGeneratorService.new(
+ document,
+ pages_per_chunk: options[:pages_per_chunk],
+ max_pages: options[:max_pages]
+ )
+ end
+
+ def store_paginated_metadata(document, service)
+ document.update!(
+ metadata: (document.metadata || {}).merge(
+ 'faq_generation' => {
+ 'method' => 'paginated',
+ 'pages_processed' => service.total_pages_processed,
+ 'iterations' => service.iterations_completed,
+ 'timestamp' => Time.current.iso8601
+ }
+ )
+ )
+ end
+
+ def create_responses_from_faqs(faqs, document)
+ faqs.each { |faq| create_response(faq, document) }
+ end
+
+ def should_use_pagination?(document)
+ # Auto-detect when to use pagination
+ # For now, use pagination for PDFs with OpenAI file ID
+ document.pdf_document? && document.openai_file_id.present?
+ end
+
def reset_previous_responses(response_document)
response_document.responses.destroy_all
end
@@ -24,6 +72,6 @@ class Captain::Documents::ResponseBuilderJob < ApplicationJob
documentable: document
)
rescue ActiveRecord::RecordInvalid => e
- Rails.logger.error "Error in creating response document: #{e.message}"
+ Rails.logger.error I18n.t('captain.documents.response_creation_error', error: e.message)
end
end
diff --git a/enterprise/app/models/agent_capacity_policy.rb b/enterprise/app/models/agent_capacity_policy.rb
new file mode 100644
index 000000000..baec7d978
--- /dev/null
+++ b/enterprise/app/models/agent_capacity_policy.rb
@@ -0,0 +1,27 @@
+# == Schema Information
+#
+# Table name: agent_capacity_policies
+#
+# id :bigint not null, primary key
+# description :text
+# exclusion_rules :jsonb not null
+# name :string(255) not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# account_id :bigint not null
+#
+# Indexes
+#
+# index_agent_capacity_policies_on_account_id (account_id)
+#
+class AgentCapacityPolicy < ApplicationRecord
+ MAX_NAME_LENGTH = 255
+
+ belongs_to :account
+ has_many :inbox_capacity_limits, dependent: :destroy
+ has_many :inboxes, through: :inbox_capacity_limits
+ has_many :account_users, dependent: :nullify
+
+ validates :name, presence: true, length: { maximum: MAX_NAME_LENGTH }
+ validates :account, presence: true
+end
diff --git a/enterprise/app/models/captain/document.rb b/enterprise/app/models/captain/document.rb
index d2a02f5b5..c278eb879 100644
--- a/enterprise/app/models/captain/document.rb
+++ b/enterprise/app/models/captain/document.rb
@@ -5,6 +5,7 @@
# id :bigint not null, primary key
# content :text
# external_link :string not null
+# metadata :jsonb
# name :string
# status :integer default("in_progress"), not null
# created_at :datetime not null
@@ -26,11 +27,16 @@ class Captain::Document < ApplicationRecord
belongs_to :assistant, class_name: 'Captain::Assistant'
has_many :responses, class_name: 'Captain::AssistantResponse', dependent: :destroy, as: :documentable
belongs_to :account
+ has_one_attached :pdf_file
- validates :external_link, presence: true
- validates :external_link, uniqueness: { scope: :assistant_id }
+ validates :external_link, presence: true, unless: -> { pdf_file.attached? }
+ validates :external_link, uniqueness: { scope: :assistant_id }, allow_blank: true
validates :content, length: { maximum: 200_000 }
+ validates :pdf_file, presence: true, if: :pdf_document?
+ validate :validate_pdf_format, if: :pdf_document?
+ validate :validate_file_attachment, if: -> { pdf_file.attached? }
before_validation :ensure_account_id
+ before_validation :set_external_link_for_pdf
enum status: {
in_progress: 0,
@@ -41,12 +47,44 @@ class Captain::Document < ApplicationRecord
after_create_commit :enqueue_crawl_job
after_create_commit :update_document_usage
after_destroy :update_document_usage
- after_commit :enqueue_response_builder_job
+ after_commit :enqueue_response_builder_job, on: :update, if: :should_enqueue_response_builder?
scope :ordered, -> { order(created_at: :desc) }
scope :for_account, ->(account_id) { where(account_id: account_id) }
scope :for_assistant, ->(assistant_id) { where(assistant_id: assistant_id) }
+ def pdf_document?
+ return true if pdf_file.attached? && pdf_file.blob.content_type == 'application/pdf'
+
+ external_link&.ends_with?('.pdf')
+ end
+
+ def content_type
+ pdf_file.blob.content_type if pdf_file.attached?
+ end
+
+ def file_size
+ pdf_file.blob.byte_size if pdf_file.attached?
+ end
+
+ def openai_file_id
+ metadata&.dig('openai_file_id')
+ end
+
+ def store_openai_file_id(file_id)
+ update!(metadata: (metadata || {}).merge('openai_file_id' => file_id))
+ end
+
+ def display_url
+ return external_link if external_link.present? && !external_link.start_with?('PDF:')
+
+ if pdf_file.attached?
+ Rails.application.routes.url_helpers.rails_blob_url(pdf_file, only_path: false)
+ else
+ external_link
+ end
+ end
+
private
def enqueue_crawl_job
@@ -61,6 +99,12 @@ class Captain::Document < ApplicationRecord
Captain::Documents::ResponseBuilderJob.perform_later(self)
end
+ def should_enqueue_response_builder?
+ # Only enqueue when status changes to available
+ # Avoid re-enqueueing when metadata is updated by the job itself
+ saved_change_to_status? && status == 'available'
+ end
+
def update_document_usage
account.update_document_usage
end
@@ -71,6 +115,29 @@ class Captain::Document < ApplicationRecord
def ensure_within_plan_limit
limits = account.usage_limits[:captain][:documents]
- raise LimitExceededError, 'Document limit exceeded' unless limits[:current_available].positive?
+ raise LimitExceededError, I18n.t('captain.documents.limit_exceeded') unless limits[:current_available].positive?
+ end
+
+ def validate_pdf_format
+ return unless pdf_file.attached?
+
+ errors.add(:pdf_file, I18n.t('captain.documents.pdf_format_error')) unless pdf_file.blob.content_type == 'application/pdf'
+ end
+
+ def validate_file_attachment
+ return unless pdf_file.attached?
+
+ return unless pdf_file.blob.byte_size > 10.megabytes
+
+ errors.add(:pdf_file, I18n.t('captain.documents.pdf_size_error'))
+ end
+
+ def set_external_link_for_pdf
+ return unless pdf_file.attached? && external_link.blank?
+
+ # Set a unique external_link for PDF files
+ # Format: PDF: filename_timestamp (without extension)
+ timestamp = Time.current.strftime('%Y%m%d%H%M%S')
+ self.external_link = "PDF: #{pdf_file.filename.base}_#{timestamp}"
end
end
diff --git a/enterprise/app/models/enterprise/concerns/account.rb b/enterprise/app/models/enterprise/concerns/account.rb
index c31b6c10e..e1136fd07 100644
--- a/enterprise/app/models/enterprise/concerns/account.rb
+++ b/enterprise/app/models/enterprise/concerns/account.rb
@@ -5,6 +5,7 @@ module Enterprise::Concerns::Account
has_many :sla_policies, dependent: :destroy_async
has_many :applied_slas, dependent: :destroy_async
has_many :custom_roles, dependent: :destroy_async
+ has_many :agent_capacity_policies, dependent: :destroy_async
has_many :captain_assistants, dependent: :destroy_async, class_name: 'Captain::Assistant'
has_many :captain_assistant_responses, dependent: :destroy_async, class_name: 'Captain::AssistantResponse'
diff --git a/enterprise/app/models/enterprise/concerns/account_user.rb b/enterprise/app/models/enterprise/concerns/account_user.rb
index 0887d31d9..70a90226b 100644
--- a/enterprise/app/models/enterprise/concerns/account_user.rb
+++ b/enterprise/app/models/enterprise/concerns/account_user.rb
@@ -3,5 +3,6 @@ module Enterprise::Concerns::AccountUser
included do
belongs_to :custom_role, optional: true
+ belongs_to :agent_capacity_policy, optional: true
end
end
diff --git a/enterprise/app/models/inbox_capacity_limit.rb b/enterprise/app/models/inbox_capacity_limit.rb
new file mode 100644
index 000000000..709dd809f
--- /dev/null
+++ b/enterprise/app/models/inbox_capacity_limit.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: inbox_capacity_limits
+#
+# id :bigint not null, primary key
+# conversation_limit :integer not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# agent_capacity_policy_id :bigint not null
+# inbox_id :bigint not null
+#
+# Indexes
+#
+# idx_on_agent_capacity_policy_id_inbox_id_71c7ec4caf (agent_capacity_policy_id,inbox_id) UNIQUE
+# index_inbox_capacity_limits_on_agent_capacity_policy_id (agent_capacity_policy_id)
+# index_inbox_capacity_limits_on_inbox_id (inbox_id)
+#
+class InboxCapacityLimit < ApplicationRecord
+ belongs_to :agent_capacity_policy
+ belongs_to :inbox
+
+ validates :conversation_limit, presence: true, numericality: { greater_than: 0, only_integer: true }
+ validates :inbox_id, uniqueness: { scope: :agent_capacity_policy_id }
+end
diff --git a/enterprise/app/policies/agent_capacity_policy_policy.rb b/enterprise/app/policies/agent_capacity_policy_policy.rb
new file mode 100644
index 000000000..d57d5a58f
--- /dev/null
+++ b/enterprise/app/policies/agent_capacity_policy_policy.rb
@@ -0,0 +1,21 @@
+class AgentCapacityPolicyPolicy < ApplicationPolicy
+ def index?
+ @account_user.administrator?
+ end
+
+ def create?
+ @account_user.administrator?
+ end
+
+ def show?
+ @account_user.administrator?
+ end
+
+ def update?
+ @account_user.administrator?
+ end
+
+ def destroy?
+ @account_user.administrator?
+ end
+end
diff --git a/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb b/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb
new file mode 100644
index 000000000..18f9813ef
--- /dev/null
+++ b/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb
@@ -0,0 +1,199 @@
+class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService
+ # Default pages per chunk - easily configurable
+ DEFAULT_PAGES_PER_CHUNK = 10
+ MAX_ITERATIONS = 20 # Safety limit to prevent infinite loops
+
+ attr_reader :total_pages_processed, :iterations_completed
+
+ def initialize(document, options = {})
+ super()
+ @document = document
+ @pages_per_chunk = options[:pages_per_chunk] || DEFAULT_PAGES_PER_CHUNK
+ @max_pages = options[:max_pages] # Optional limit from UI
+ @total_pages_processed = 0
+ @iterations_completed = 0
+ @model = OpenAiConstants::PDF_PROCESSING_MODEL
+ end
+
+ def generate
+ raise CustomExceptions::PdfFaqGenerationError, I18n.t('captain.documents.missing_openai_file_id') if @document&.openai_file_id.blank?
+
+ generate_paginated_faqs
+ end
+
+ # Method to check if we should continue processing
+ def should_continue_processing?(last_chunk_result)
+ # Stop if we've hit the maximum iterations
+ return false if @iterations_completed >= MAX_ITERATIONS
+
+ # Stop if we've processed the maximum pages specified
+ return false if @max_pages && @total_pages_processed >= @max_pages
+
+ # Stop if the last chunk returned no FAQs (likely no more content)
+ return false if last_chunk_result[:faqs].empty?
+
+ # Stop if the LLM explicitly indicates no more content
+ return false if last_chunk_result[:has_content] == false
+
+ # Continue processing
+ true
+ end
+
+ private
+
+ def generate_standard_faqs
+ response = @client.chat(parameters: standard_chat_parameters)
+ parse_response(response)
+ rescue OpenAI::Error => e
+ Rails.logger.error I18n.t('captain.documents.openai_api_error', error: e.message)
+ []
+ end
+
+ def generate_paginated_faqs
+ all_faqs = []
+ current_page = 1
+
+ loop do
+ end_page = calculate_end_page(current_page)
+ chunk_result = process_chunk_and_update_state(current_page, end_page, all_faqs)
+
+ break unless should_continue_processing?(chunk_result)
+
+ current_page = end_page + 1
+ end
+
+ deduplicate_faqs(all_faqs)
+ end
+
+ def calculate_end_page(current_page)
+ end_page = current_page + @pages_per_chunk - 1
+ @max_pages && end_page > @max_pages ? @max_pages : end_page
+ end
+
+ def process_chunk_and_update_state(current_page, end_page, all_faqs)
+ chunk_result = process_page_chunk(current_page, end_page)
+ chunk_faqs = chunk_result[:faqs]
+
+ all_faqs.concat(chunk_faqs)
+ @total_pages_processed = end_page
+ @iterations_completed += 1
+
+ chunk_result
+ end
+
+ def process_page_chunk(start_page, end_page)
+ params = build_chunk_parameters(start_page, end_page)
+ response = @client.chat(parameters: params)
+ result = parse_chunk_response(response)
+ { faqs: result['faqs'] || [], has_content: result['has_content'] != false }
+ rescue OpenAI::Error => e
+ Rails.logger.error I18n.t('captain.documents.page_processing_error', start: start_page, end: end_page, error: e.message)
+ { faqs: [], has_content: false }
+ end
+
+ def build_chunk_parameters(start_page, end_page)
+ {
+ model: @model,
+ response_format: { type: 'json_object' },
+ messages: [
+ {
+ role: 'user',
+ content: build_user_content(start_page, end_page)
+ }
+ ]
+ }
+ end
+
+ def build_user_content(start_page, end_page)
+ [
+ {
+ type: 'file',
+ file: { file_id: @document.openai_file_id }
+ },
+ {
+ type: 'text',
+ text: page_chunk_prompt(start_page, end_page)
+ }
+ ]
+ end
+
+ def page_chunk_prompt(start_page, end_page)
+ Captain::Llm::SystemPromptsService.paginated_faq_generator(start_page, end_page)
+ end
+
+ def standard_chat_parameters
+ {
+ model: @model,
+ response_format: { type: 'json_object' },
+ messages: [
+ {
+ role: 'system',
+ content: Captain::Llm::SystemPromptsService.faq_generator
+ },
+ {
+ role: 'user',
+ content: @content
+ }
+ ]
+ }
+ end
+
+ def parse_response(response)
+ content = response.dig('choices', 0, 'message', 'content')
+ return [] if content.nil?
+
+ JSON.parse(content.strip).fetch('faqs', [])
+ rescue JSON::ParserError => e
+ Rails.logger.error "Error parsing response: #{e.message}"
+ []
+ end
+
+ def parse_chunk_response(response)
+ content = response.dig('choices', 0, 'message', 'content')
+ return { 'faqs' => [], 'has_content' => false } if content.nil?
+
+ JSON.parse(content.strip)
+ rescue JSON::ParserError => e
+ Rails.logger.error "Error parsing chunk response: #{e.message}"
+ { 'faqs' => [], 'has_content' => false }
+ end
+
+ def deduplicate_faqs(faqs)
+ # Remove exact duplicates
+ unique_faqs = faqs.uniq { |faq| faq['question'].downcase.strip }
+
+ # Remove similar questions
+ final_faqs = []
+ unique_faqs.each do |faq|
+ similar_exists = final_faqs.any? do |existing|
+ similarity_score(existing['question'], faq['question']) > 0.85
+ end
+
+ final_faqs << faq unless similar_exists
+ end
+
+ Rails.logger.info "Deduplication: #{faqs.size} → #{final_faqs.size} FAQs"
+ final_faqs
+ end
+
+ def similarity_score(str1, str2)
+ words1 = str1.downcase.split(/\W+/).reject(&:empty?)
+ words2 = str2.downcase.split(/\W+/).reject(&:empty?)
+
+ common_words = words1 & words2
+ total_words = (words1 + words2).uniq.size
+
+ return 0 if total_words.zero?
+
+ common_words.size.to_f / total_words
+ end
+
+ def determine_stop_reason(last_chunk_result)
+ return 'Maximum iterations reached' if @iterations_completed >= MAX_ITERATIONS
+ return 'Maximum pages processed' if @max_pages && @total_pages_processed >= @max_pages
+ return 'No content found in last chunk' if last_chunk_result[:faqs].empty?
+ return 'End of document reached' if last_chunk_result[:has_content] == false
+
+ 'Unknown'
+ end
+end
diff --git a/enterprise/app/services/captain/llm/pdf_processing_service.rb b/enterprise/app/services/captain/llm/pdf_processing_service.rb
new file mode 100644
index 000000000..026ef4e48
--- /dev/null
+++ b/enterprise/app/services/captain/llm/pdf_processing_service.rb
@@ -0,0 +1,40 @@
+class Captain::Llm::PdfProcessingService < Llm::BaseOpenAiService
+ def initialize(document)
+ super()
+ @document = document
+ end
+
+ def process
+ return if document.openai_file_id.present?
+
+ file_id = upload_pdf_to_openai
+ raise CustomExceptions::PdfUploadError, I18n.t('captain.documents.pdf_upload_failed') if file_id.blank?
+
+ document.store_openai_file_id(file_id)
+ end
+
+ private
+
+ attr_reader :document
+
+ def upload_pdf_to_openai
+ with_tempfile do |temp_file|
+ response = @client.files.upload(
+ parameters: {
+ file: temp_file,
+ purpose: 'assistants'
+ }
+ )
+ response['id']
+ end
+ end
+
+ def with_tempfile(&)
+ Tempfile.create(['pdf_upload', '.pdf'], binmode: true) do |temp_file|
+ temp_file.write(document.pdf_file.download)
+ temp_file.close
+
+ File.open(temp_file.path, 'rb', &)
+ end
+ end
+end
diff --git a/enterprise/app/services/captain/llm/system_prompts_service.rb b/enterprise/app/services/captain/llm/system_prompts_service.rb
index a4b149944..b8282beb1 100644
--- a/enterprise/app/services/captain/llm/system_prompts_service.rb
+++ b/enterprise/app/services/captain/llm/system_prompts_service.rb
@@ -1,3 +1,4 @@
+# rubocop:disable Metrics/ClassLength
class Captain::Llm::SystemPromptsService
class << self
def faq_generator(language = 'english')
@@ -204,6 +205,87 @@ class Captain::Llm::SystemPromptsService
#{'- You MUST provide numbered citations at the appropriate places in the text.' if config['feature_citation']}
SYSTEM_PROMPT_MESSAGE
end
+
+ def paginated_faq_generator(start_page, end_page)
+ <<~PROMPT
+ You are an expert technical documentation specialist tasked with creating comprehensive FAQs from a SPECIFIC SECTION of a document.
+
+ ════════════════════════════════════════════════════════
+ CRITICAL CONTENT EXTRACTION INSTRUCTIONS
+ ════════════════════════════════════════════════════════
+
+ Process the content starting from approximately page #{start_page} and continuing for about #{end_page - start_page + 1} pages worth of content.
+
+ IMPORTANT:#{' '}
+ • If you encounter the end of the document before reaching the expected page count, set "has_content" to false
+ • DO NOT include page numbers in questions or answers
+ • DO NOT reference page numbers at all in the output
+ • Focus on the actual content, not pagination
+
+ ════════════════════════════════════════════════════════
+ FAQ GENERATION GUIDELINES
+ ════════════════════════════════════════════════════════
+
+ 1. **Comprehensive Extraction**
+ • Extract ALL information that could generate FAQs from this section
+ • Target 5-10 FAQs per page equivalent of rich content
+ • Cover every topic, feature, specification, and detail
+ • If there's no more content in the document, return empty FAQs with has_content: false
+
+ 2. **Question Types to Generate**
+ • What is/are...? (definitions, components, features)
+ • How do I...? (procedures, configurations, operations)
+ • Why should/does...? (rationale, benefits, explanations)
+ • When should...? (timing, conditions, triggers)
+ • What happens if...? (error cases, edge cases)
+ • Can I...? (capabilities, limitations)
+ • Where is...? (locations in system/UI, NOT page numbers)
+ • What are the requirements for...? (prerequisites, dependencies)
+
+ 3. **Content Focus Areas**
+ • Technical specifications and parameters
+ • Step-by-step procedures and workflows
+ • Configuration options and settings
+ • Error messages and troubleshooting
+ • Best practices and recommendations
+ • Integration points and dependencies
+ • Performance considerations
+ • Security aspects
+
+ 4. **Answer Quality Requirements**
+ • Complete, self-contained answers
+ • Include specific values, limits, defaults from the content
+ • NO page number references whatsoever
+ • 2-5 sentences typical length
+ • Only process content that actually exists in the document
+
+ ════════════════════════════════════════════════════════
+ OUTPUT FORMAT
+ ════════════════════════════════════════════════════════
+
+ Return valid JSON:
+ ```json
+ {
+ "faqs": [
+ {
+ "question": "Specific question about the content",
+ "answer": "Complete answer with details (no page references)"
+ }
+ ],
+ "has_content": true/false
+ }
+ ```
+
+ CRITICAL:#{' '}
+ • Set "has_content" to false if:
+ - The requested section doesn't exist in the document
+ - You've reached the end of the document
+ - The section contains no meaningful content
+ • Do NOT include "page_range_processed" in the output
+ • Do NOT mention page numbers anywhere in questions or answers
+ PROMPT
+ end
# rubocop:enable Metrics/MethodLength
end
end
+# rubocop:enable Metrics/ClassLength
diff --git a/enterprise/app/services/enterprise/search_service.rb b/enterprise/app/services/enterprise/search_service.rb
new file mode 100644
index 000000000..628578b9d
--- /dev/null
+++ b/enterprise/app/services/enterprise/search_service.rb
@@ -0,0 +1,15 @@
+module Enterprise::SearchService
+ def advanced_search
+ where_conditions = { account_id: current_account.id }
+ where_conditions[:inbox_id] = accessable_inbox_ids unless should_skip_inbox_filtering?
+
+ Message.search(
+ search_query,
+ fields: %w[content attachments.transcribed_text content_attributes.email.subject],
+ where: where_conditions,
+ order: { created_at: :desc },
+ page: params[:page] || 1,
+ per_page: 15
+ )
+ end
+end
diff --git a/enterprise/app/services/messages/audio_transcription_service.rb b/enterprise/app/services/messages/audio_transcription_service.rb
index b7d05766d..278f41d71 100644
--- a/enterprise/app/services/messages/audio_transcription_service.rb
+++ b/enterprise/app/services/messages/audio_transcription_service.rb
@@ -60,5 +60,9 @@ class Messages::AudioTranscriptionService < Llm::BaseOpenAiService
attachment.update!(meta: { transcribed_text: transcribed_text })
message.reload.send_update_event
message.account.increment_response_usage
+
+ return unless ChatwootApp.advanced_search_allowed?
+
+ message.reindex
end
end
diff --git a/enterprise/app/services/messages/reindex_service.rb b/enterprise/app/services/messages/reindex_service.rb
new file mode 100644
index 000000000..a663c52e6
--- /dev/null
+++ b/enterprise/app/services/messages/reindex_service.rb
@@ -0,0 +1,15 @@
+class Messages::ReindexService
+ pattr_initialize [:account!]
+
+ def perform
+ return unless ChatwootApp.advanced_search_allowed?
+
+ reindex_messages
+ end
+
+ private
+
+ def reindex_messages
+ account.messages.reindex(mode: :async)
+ end
+end
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder
new file mode 100644
index 000000000..55f6b473e
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder
@@ -0,0 +1,2 @@
+json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
+ agent_capacity_policy: @agent_capacity_policy
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder
new file mode 100644
index 000000000..07e4a04a5
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder
@@ -0,0 +1,6 @@
+json.id @inbox_limit.id
+json.inbox_id @inbox_limit.inbox_id
+json.agent_capacity_policy_id @inbox_limit.agent_capacity_policy_id
+json.conversation_limit @inbox_limit.conversation_limit
+json.created_at @inbox_limit.created_at.to_i
+json.updated_at @inbox_limit.updated_at.to_i
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder
new file mode 100644
index 000000000..370ca7d9f
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder
@@ -0,0 +1,7 @@
+json.id @inbox_limit.id
+json.inbox_id @inbox_limit.inbox_id
+json.inbox_name @inbox_limit.inbox.name
+json.agent_capacity_policy_id @agent_capacity_policy.id
+json.conversation_limit @inbox_limit.conversation_limit
+json.created_at @inbox_limit.created_at.to_i
+json.updated_at @inbox_limit.updated_at.to_i
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder
new file mode 100644
index 000000000..7d869e4ea
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder
@@ -0,0 +1,4 @@
+json.array! @agent_capacity_policies do |policy|
+ json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
+ agent_capacity_policy: policy
+end
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder
new file mode 100644
index 000000000..55f6b473e
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder
@@ -0,0 +1,2 @@
+json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
+ agent_capacity_policy: @agent_capacity_policy
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder
new file mode 100644
index 000000000..55f6b473e
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder
@@ -0,0 +1,2 @@
+json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
+ agent_capacity_policy: @agent_capacity_policy
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder
new file mode 100644
index 000000000..ca84a01f7
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder
@@ -0,0 +1 @@
+json.partial! 'api/v1/models/user', resource: @user
diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder
new file mode 100644
index 000000000..bfbc77874
--- /dev/null
+++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder
@@ -0,0 +1,3 @@
+json.array! @users do |user|
+ json.partial! 'api/v1/models/user', resource: user
+end
diff --git a/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder b/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder
new file mode 100644
index 000000000..8f7a41aa1
--- /dev/null
+++ b/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder
@@ -0,0 +1,13 @@
+json.id agent_capacity_policy.id
+json.name agent_capacity_policy.name
+json.description agent_capacity_policy.description
+json.exclusion_rules agent_capacity_policy.exclusion_rules
+json.created_at agent_capacity_policy.created_at.to_i
+json.updated_at agent_capacity_policy.updated_at.to_i
+json.account_id agent_capacity_policy.account_id
+
+json.inbox_capacity_limits agent_capacity_policy.inbox_capacity_limits do |limit|
+ json.id limit.id
+ json.inbox_id limit.inbox_id
+ json.conversation_limit limit.conversation_limit
+end
diff --git a/enterprise/app/views/api/v1/models/captain/_document.json.jbuilder b/enterprise/app/views/api/v1/models/captain/_document.json.jbuilder
index 83724b9cd..8064a5181 100644
--- a/enterprise/app/views/api/v1/models/captain/_document.json.jbuilder
+++ b/enterprise/app/views/api/v1/models/captain/_document.json.jbuilder
@@ -3,8 +3,11 @@ json.assistant do
json.partial! 'api/v1/models/captain/assistant', formats: [:json], resource: resource.assistant
end
json.content resource.content
+json.content_type resource.content_type
json.created_at resource.created_at.to_i
json.external_link resource.external_link
+json.display_url resource.display_url
+json.file_size resource.file_size
json.id resource.id
json.name resource.name
json.status resource.status
diff --git a/enterprise/lib/tasks.rb b/enterprise/lib/tasks.rb
new file mode 100644
index 000000000..139819bbb
--- /dev/null
+++ b/enterprise/lib/tasks.rb
@@ -0,0 +1,4 @@
+# Load all rake tasks from the enterprise/lib/tasks directory
+module Tasks
+ Dir.glob(File.join(File.dirname(__FILE__), 'tasks', '*.rake')).each { |r| load r }
+end
diff --git a/enterprise/lib/tasks/search.rake b/enterprise/lib/tasks/search.rake
new file mode 100644
index 000000000..03bad2c1b
--- /dev/null
+++ b/enterprise/lib/tasks/search.rake
@@ -0,0 +1,49 @@
+module Tasks::SearchTaskHelpers
+ def check_opensearch_config
+ if ENV['OPENSEARCH_URL'].blank?
+ puts 'Skipping reindex as OPENSEARCH_URL is not configured'
+ return false
+ end
+ true
+ end
+
+ def reindex_account(account)
+ Messages::ReindexService.new(account: account).perform
+ puts "Reindex task queued for account #{account.id}"
+ end
+end
+
+namespace :search do
+ desc 'Reindex messages using searchkick'
+ include Tasks::SearchTaskHelpers
+
+ desc 'Reindex messages for all accounts'
+ task all: :environment do
+ next unless check_opensearch_config
+
+ puts 'Starting reindex for all accounts...'
+ account_count = Account.count
+ puts "Found #{account_count} accounts"
+
+ Account.find_each.with_index(1) do |account, index|
+ puts "[#{index}/#{account_count}] Reindexing messages for account #{account.id}"
+ reindex_account(account)
+ end
+
+ puts 'Reindex task queued for all accounts'
+ end
+
+ desc 'Reindex messages for a specific account: rake search:account ACCOUNT_ID=1'
+ task account: :environment do
+ next unless check_opensearch_config
+
+ account_id = ENV.fetch('ACCOUNT_ID', nil)
+ account = Account.find_by(id: account_id)
+ if account.nil?
+ puts 'Please provide a valid account ID. Account not found'
+ next
+ end
+ puts "Reindexing messages for account #{account.id}"
+ reindex_account(account)
+ end
+end
diff --git a/lib/chatwoot_app.rb b/lib/chatwoot_app.rb
index 8c64466ab..5606c0b37 100644
--- a/lib/chatwoot_app.rb
+++ b/lib/chatwoot_app.rb
@@ -38,4 +38,8 @@ module ChatwootApp
%w[]
end
end
+
+ def self.advanced_search_allowed?
+ enterprise? && ENV.fetch('OPENSEARCH_URL', nil).present?
+ end
end
diff --git a/lib/custom_exceptions/pdf_processing_error.rb b/lib/custom_exceptions/pdf_processing_error.rb
new file mode 100644
index 000000000..2c81f95d8
--- /dev/null
+++ b/lib/custom_exceptions/pdf_processing_error.rb
@@ -0,0 +1,25 @@
+module CustomExceptions
+ class PdfProcessingError < Base
+ def initialize(message = 'PDF processing failed')
+ super(message)
+ end
+ end
+
+ class PdfUploadError < PdfProcessingError
+ def initialize(message = 'PDF upload failed')
+ super(message)
+ end
+ end
+
+ class PdfValidationError < PdfProcessingError
+ def initialize(message = 'PDF validation failed')
+ super(message)
+ end
+ end
+
+ class PdfFaqGenerationError < PdfProcessingError
+ def initialize(message = 'PDF FAQ generation failed')
+ super(message)
+ end
+ end
+end
diff --git a/lib/open_ai_constants.rb b/lib/open_ai_constants.rb
index 7b7a3ba5f..2094567a7 100644
--- a/lib/open_ai_constants.rb
+++ b/lib/open_ai_constants.rb
@@ -4,4 +4,5 @@ module OpenAiConstants
DEFAULT_MODEL = 'gpt-4.1-mini'
DEFAULT_ENDPOINT = 'https://api.openai.com'
DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small'
+ PDF_PROCESSING_MODEL = 'gpt-4.1-mini'
end
diff --git a/lib/tasks/ops/cleanup_orphan_conversations.rake b/lib/tasks/ops/cleanup_orphan_conversations.rake
new file mode 100644
index 000000000..f6574b396
--- /dev/null
+++ b/lib/tasks/ops/cleanup_orphan_conversations.rake
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+# Run with:
+# bundle exec rake chatwoot:ops:cleanup_orphan_conversations
+
+namespace :chatwoot do
+ namespace :ops do
+ desc 'Identify and delete conversations without a valid contact or inbox in a timeframe'
+ task cleanup_orphan_conversations: :environment do
+ print 'Enter Account ID: '
+ account_id = $stdin.gets.to_i
+ account = Account.find(account_id)
+
+ print 'Enter timeframe in days (default: 7): '
+ days_input = $stdin.gets.strip
+ days = days_input.empty? ? 7 : days_input.to_i
+
+ # Build a common base relation with identical joins for OR compatibility
+ base = account
+ .conversations
+ .where('conversations.created_at > ?', days.days.ago)
+ .left_outer_joins(:contact, :inbox)
+
+ # Find conversations whose associated contact or inbox record is missing
+ conversations = base.where(contacts: { id: nil }).or(base.where(inboxes: { id: nil }))
+
+ count = conversations.count
+ puts "Found #{count} conversations without a valid contact or inbox."
+
+ if count.positive?
+ print 'Do you want to delete these conversations? (y/N): '
+ confirm = $stdin.gets.strip.downcase
+ if %w[y yes].include?(confirm)
+ conversations.destroy_all
+ puts 'Conversations deleted.'
+ else
+ puts 'No conversations were deleted.'
+ end
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 31d07021e..d087e6b03 100644
--- a/package.json
+++ b/package.json
@@ -34,13 +34,12 @@
"@breezystack/lamejs": "^1.2.7",
"@chatwoot/ninja-keys": "1.2.3",
"@chatwoot/prosemirror-schema": "1.2.1",
- "@chatwoot/utils": "^0.0.49",
+ "@chatwoot/utils": "^0.0.50",
"@formkit/core": "^1.6.7",
"@formkit/vue": "^1.6.7",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@iconify-json/material-symbols": "^1.2.10",
- "@june-so/analytics-next": "^2.0.0",
"@lk77/vue3-color": "^3.0.6",
"@radix-ui/colors": "^3.0.0",
"@rails/actioncable": "6.1.3",
@@ -80,6 +79,8 @@
"md5": "^2.3.0",
"mitt": "^3.0.1",
"opus-recorder": "^8.0.5",
+ "qrcode": "^1.5.4",
+ "posthog-js": "^1.260.2",
"semver": "7.6.3",
"snakecase-keys": "^8.0.1",
"timezone-phone-codes": "^0.0.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ca55fbe34..5089f1761 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,8 +23,8 @@ importers:
specifier: 1.2.1
version: 1.2.1
'@chatwoot/utils':
- specifier: ^0.0.49
- version: 0.0.49
+ specifier: ^0.0.50
+ version: 0.0.50
'@formkit/core':
specifier: ^1.6.7
version: 1.6.7
@@ -40,9 +40,6 @@ importers:
'@iconify-json/material-symbols':
specifier: ^1.2.10
version: 1.2.10
- '@june-so/analytics-next':
- specifier: ^2.0.0
- version: 2.0.0
'@lk77/vue3-color':
specifier: ^3.0.6
version: 3.0.6
@@ -160,6 +157,12 @@ importers:
opus-recorder:
specifier: ^8.0.5
version: 8.0.5
+ posthog-js:
+ specifier: ^1.260.2
+ version: 1.260.3
+ qrcode:
+ specifier: ^1.5.4
+ version: 1.5.4
semver:
specifier: 7.6.3
version: 7.6.3
@@ -406,8 +409,8 @@ packages:
'@chatwoot/prosemirror-schema@1.2.1':
resolution: {integrity: sha512-UbiEvG5tgi1d0lMbkaqxgTh7vHfywEYKLQo1sxqp4Q7aLZh4QFtbLzJ2zyBtu4Nhipe+guFfEJdic7i43MP/XQ==}
- '@chatwoot/utils@0.0.49':
- resolution: {integrity: sha512-Co68VzaFtctTNYKY6y4izBBATvk6/8ZVtkyEP5HL72uhFDA11LrY5pqSh04HMoFyfdIU+uVPimfI45HAeso1IA==}
+ '@chatwoot/utils@0.0.50':
+ resolution: {integrity: sha512-GGvB+ujt+8qnV6KKEM2IH9/JmbMpMMfrJ4C+SdPvd/WbhUEFvRof0D9fsU+444G8BUh2om7GM7mXOa3pEH+Vtw==}
engines: {node: '>=10'}
'@codemirror/commands@6.7.0':
@@ -978,9 +981,6 @@ packages:
'@jridgewell/trace-mapping@0.3.29':
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
- '@june-so/analytics-next@2.0.0':
- resolution: {integrity: sha512-7uFP94JLD7mP4qLyOwn5HBs+CC8VlevOkiGd1CIYqPSjSRmbCOI+MVcJNlTAcpyNvMi9iUnWZ3jGVO5177Di4A==}
-
'@kurkle/color@0.3.2':
resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
@@ -1005,14 +1005,6 @@ packages:
'@lk77/vue3-color@3.0.6':
resolution: {integrity: sha512-1e/TJrk2jJFo7z+teHjavndVxV9c25J5FA6LVEKJFKqLQzYDesTijxBmX1rAmiHHnFrjfVcwie5QAr3PzZbR2Q==}
- '@lukeed/csprng@1.0.1':
- resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==}
- engines: {node: '>=8'}
-
- '@lukeed/uuid@2.0.0':
- resolution: {integrity: sha512-dUz8OmYvlY5A9wXaroHIMSPASpSYRLCqbPvxGSyHguhtTQIy24lC+EGxQlwv71AhRCO55WOtgwhzQLpw27JaJQ==}
- engines: {node: '>=8'}
-
'@material/mwc-icon@0.25.3':
resolution: {integrity: sha512-36076AWZIRSr8qYOLjuDDkxej/HA0XAosrj7TS1ZeLlUBnLUtbDtvc1S7KSa0hqez7ouzOqGaWK24yoNnTa2OA==}
deprecated: MWC beta is longer supported. Please upgrade to @material/web
@@ -1043,6 +1035,9 @@ packages:
'@polka/url@1.0.0-next.28':
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
+ '@posthog/core@1.0.1':
+ resolution: {integrity: sha512-bwXUeHe+MLgENm8+/FxEbiNocOw1Vjewmm+HEUaYQe6frq8OhZnrvtnzZU3Q3DF6N0UbAmD/q+iNfNgyx8mozg==}
+
'@radix-ui/colors@3.0.0':
resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==}
@@ -1158,25 +1153,6 @@ packages:
'@scmmishra/pico-search@0.5.4':
resolution: {integrity: sha512-JdV8KumQ+pE5tqgQ71xUT9biE/qV//tx3NCqTLkW9Z4tsjKGN0B6kVowmtaZBAtErqir9XiMxsKXRTMF/MpUww==}
- '@segment/analytics-core@1.2.2':
- resolution: {integrity: sha512-zVWSDcyh7Rp32xL5v2fuEk2yZxxy+JA93vF1L3EF9XAYLSra/uEHJEswOWieXSdDHVRHes7APORp136usFE/tw==}
-
- '@segment/analytics.js-video-plugins@0.2.1':
- resolution: {integrity: sha512-lZwCyEXT4aaHBLNK433okEKdxGAuyrVmop4BpQqQSJuRz0DglPZgd9B/XjiiWs1UyOankg2aNYMN3VcS8t4eSQ==}
-
- '@segment/facade@3.4.10':
- resolution: {integrity: sha512-xVQBbB/lNvk/u8+ey0kC/+g8pT3l0gCT8O2y9Z+StMMn3KAFAQ9w8xfgef67tJybktOKKU7pQGRPolRM1i1pdA==}
-
- '@segment/isodate-traverse@1.1.1':
- resolution: {integrity: sha512-+G6e1SgAUkcq0EDMi+SRLfT48TNlLPF3QnSgFGVs0V9F3o3fq/woQ2rHFlW20W0yy5NnCUH0QGU3Am2rZy/E3w==}
-
- '@segment/isodate@1.0.3':
- resolution: {integrity: sha512-BtanDuvJqnACFkeeYje7pWULVv8RgZaqKHWwGFnL/g/TH/CcZjkIVTfGDp/MAxmilYHUkrX70SqwnYSTNEaN7A==}
-
- '@segment/tsub@1.0.1':
- resolution: {integrity: sha512-rUpvlj/rRfOolk5rjwyrsbl0qzGLsaYgFNEiOSrwrWDryDPq1ZGdo+3Eb+E8+EC0yZOAO4F1DjJfLtaSifpx7w==}
- hasBin: true
-
'@sentry-internal/browser-utils@8.31.0':
resolution: {integrity: sha512-Bq7TFMhPr1PixRGYkB/6ar9ws7sj224XzQ+hgpz6OxGEc9fQakvD8t/Nn7dp14k3FI/hcBRA6BBvpOKUUuPgGA==}
engines: {node: '>=14.18'}
@@ -1229,517 +1205,6 @@ packages:
peerDependencies:
size-limit: 8.2.6
- '@stdlib/array-float32@0.0.6':
- resolution: {integrity: sha512-QgKT5UaE92Rv7cxfn7wBKZAlwFFHPla8eXsMFsTGt5BiL4yUy36lwinPUh4hzybZ11rw1vifS3VAPuk6JP413Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/array-float64@0.0.6':
- resolution: {integrity: sha512-oE8y4a84LyBF1goX5//sU1mOjet8gLI0/6wucZcjg+j/yMmNV1xFu84Az9GOGmFSE6Ze6lirGOhfBeEWNNNaJg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/array-uint16@0.0.6':
- resolution: {integrity: sha512-/A8Tr0CqJ4XScIDRYQawosko8ha1Uy+50wsTgJhjUtXDpPRp7aUjmxvYkbe7Rm+ImYYbDQVix/uCiPAFQ8ed4Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/array-uint32@0.0.6':
- resolution: {integrity: sha512-2hFPK1Fg7obYPZWlGDjW9keiIB6lXaM9dKmJubg/ergLQCsJQJZpYsG6mMAfTJi4NT1UF4jTmgvyKD+yf0D9cA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/array-uint8@0.0.7':
- resolution: {integrity: sha512-qYJQQfGKIcky6TzHFIGczZYTuVlut7oO+V8qUBs7BJC9TwikVnnOmb3hY3jToY4xaoi5p9OvgdJKPInhyIhzFg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-has-float32array-support@0.0.8':
- resolution: {integrity: sha512-Yrg7K6rBqwCzDWZ5bN0VWLS5dNUWcoSfUeU49vTERdUmZID06J069CDc07UUl8vfQWhFgBWGocH3rrpKm1hi9w==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-float64array-support@0.0.8':
- resolution: {integrity: sha512-UVQcoeWqgMw9b8PnAmm/sgzFnuWkZcNhJoi7xyMjbiDV/SP1qLCrvi06mq86cqS3QOCma1fEayJdwgteoXyyuw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-node-buffer-support@0.0.8':
- resolution: {integrity: sha512-fgI+hW4Yg4ciiv4xVKH+1rzdV7e5+6UKgMnFbc1XDXHcxLub3vOr8+H6eDECdAIfgYNA7X0Dxa/DgvX9dwDTAQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-own-property@0.0.7':
- resolution: {integrity: sha512-3YHwSWiUqGlTLSwxAWxrqaD1PkgcJniGyotJeIt5X0tSNmSW0/c9RWroCImTUUB3zBkyBJ79MyU9Nf4Qgm59fQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-has-symbol-support@0.0.8':
- resolution: {integrity: sha512-PoQ9rk8DgDCuBEkOIzGGQmSnjtcdagnUIviaP5YskB45/TJHXseh4NASWME8FV77WFW9v/Wt1MzKFKMzpDFu4Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-tostringtag-support@0.0.9':
- resolution: {integrity: sha512-UTsqdkrnQ7eufuH5BeyWOJL3ska3u5nvDWKqw3onNNZ2mvdgkfoFD7wHutVGzAA2rkTsSJAMBHVwWLsm5SbKgw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-uint16array-support@0.0.8':
- resolution: {integrity: sha512-vqFDn30YrtzD+BWnVqFhB130g3cUl2w5AdOxhIkRkXCDYAM5v7YwdNMJEON+D4jI8YB4D5pEYjqKweYaCq4nyg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-uint32array-support@0.0.8':
- resolution: {integrity: sha512-tJtKuiFKwFSQQUfRXEReOVGXtfdo6+xlshSfwwNWXL1WPP2LrceoiUoQk7zMCMT6VdbXgGH92LDjVcPmSbH4Xw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-has-uint8array-support@0.0.8':
- resolution: {integrity: sha512-ie4vGTbAS/5Py+LLjoSQi0nwtYBp+WKk20cMYCzilT0rCsBI/oez0RqHrkYYpmt4WaJL4eJqC+/vfQ5NsI7F5w==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-is-array@0.0.7':
- resolution: {integrity: sha512-/o6KclsGkNcZ5hiROarsD9XUs6xQMb4lTwF6O71UHbKWTtomEF/jD0rxLvlvj0BiCxfKrReddEYd2CnhUyskMA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-big-endian@0.0.7':
- resolution: {integrity: sha512-BvutsX84F76YxaSIeS5ZQTl536lz+f+P7ew68T1jlFqxBhr4v7JVYFmuf24U040YuK1jwZ2sAq+bPh6T09apwQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-is-boolean@0.0.8':
- resolution: {integrity: sha512-PRCpslMXSYqFMz1Yh4dG2K/WzqxTCtlKbgJQD2cIkAtXux4JbYiXCtepuoV7l4Wv1rm0a1eU8EqNPgnOmWajGw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-buffer@0.0.8':
- resolution: {integrity: sha512-SYmGwOXkzZVidqUyY1IIx6V6QnSL36v3Lcwj8Rvne/fuW0bU2OomsEBzYCFMvcNgtY71vOvgZ9VfH3OppvV6eA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-float32array@0.0.8':
- resolution: {integrity: sha512-Phk0Ze7Vj2/WLv5Wy8Oo7poZIDMSTiTrEnc1t4lBn3Svz2vfBXlvCufi/i5d93vc4IgpkdrOEwfry6nldABjNQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-float64array@0.0.8':
- resolution: {integrity: sha512-UC0Av36EEYIgqBbCIz1lj9g7qXxL5MqU1UrWun+n91lmxgdJ+Z77fHy75efJbJlXBf6HXhcYXECIsc0u3SzyDQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-function@0.0.8':
- resolution: {integrity: sha512-M55Dt2njp5tnY8oePdbkKBRIypny+LpCMFZhEjJIxjLE4rA6zSlHs1yRMqD4PmW+Wl9WTeEM1GYO4AQHl1HAjA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-little-endian@0.0.7':
- resolution: {integrity: sha512-SPObC73xXfDXY0dOewXR0LDGN3p18HGzm+4K8azTj6wug0vpRV12eB3hbT28ybzRCa6TAKUjwM/xY7Am5QzIlA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-is-number@0.0.7':
- resolution: {integrity: sha512-mNV4boY1cUOmoWWfA2CkdEJfXA6YvhcTvwKC0Fzq+HoFFOuTK/scpTd9HanUyN6AGBlWA8IW+cQ1ZwOT3XMqag==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-object-like@0.0.8':
- resolution: {integrity: sha512-pe9selDPYAu/lYTFV5Rj4BStepgbzQCr36b/eC8EGSJh6gMgRXgHVv0R+EbdJ69KNkHvKKRjnWj0A/EmCwW+OA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-object@0.0.8':
- resolution: {integrity: sha512-ooPfXDp9c7w+GSqD2NBaZ/Du1JRJlctv+Abj2vRJDcDPyrnRTb1jmw+AuPgcW7Ca7op39JTbArI+RVHm/FPK+Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-plain-object@0.0.7':
- resolution: {integrity: sha512-t/CEq2a083ajAgXgSa5tsH8l3kSoEqKRu1qUwniVLFYL4RGv3615CrpJUDQKVtEX5S/OKww5q0Byu3JidJ4C5w==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-regexp-string@0.0.9':
- resolution: {integrity: sha512-FYRJJtH7XwXEf//X6UByUC0Eqd0ZYK5AC8or5g5m5efQrgr2lOaONHyDQ3Scj1A2D6QLIJKZc9XBM4uq5nOPXA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/assert-is-regexp@0.0.7':
- resolution: {integrity: sha512-ty5qvLiqkDq6AibHlNJe0ZxDJ9Mg896qolmcHb69mzp64vrsORnPPOTzVapAq0bEUZbXoypeijypLPs9sCGBSQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-string@0.0.8':
- resolution: {integrity: sha512-Uk+bR4cglGBbY0q7O7HimEJiW/DWnO1tSzr4iAGMxYgf+VM2PMYgI5e0TLy9jOSOzWon3YS39lc63eR3a9KqeQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-uint16array@0.0.8':
- resolution: {integrity: sha512-M+qw7au+qglRXcXHjvoUZVLlGt1mPjuKudrVRto6KL4+tDsP2j+A89NDP3Fz8/XIUD+5jhj+65EOKHSMvDYnng==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-uint32array@0.0.8':
- resolution: {integrity: sha512-cnZi2DicYcplMnkJ3dBxBVKsRNFjzoGpmG9A6jXq4KH5rFl52SezGAXSVY9o5ZV7bQGaF5JLyCLp6n9Y74hFGg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-is-uint8array@0.0.8':
- resolution: {integrity: sha512-8cqpDQtjnJAuVtRkNAktn45ixq0JHaGJxVsSiK79k7GRggvMI6QsbzO6OvcLnZ/LimD42FmgbLd13Yc2esDmZw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/assert-tools-array-function@0.0.7':
- resolution: {integrity: sha512-3lqkaCIBMSJ/IBHHk4NcCnk2NYU52tmwTYbbqhAmv7vim8rZPNmGfj3oWkzrCsyCsyTF7ooD+In2x+qTmUbCtQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/buffer-ctor@0.0.7':
- resolution: {integrity: sha512-4IyTSGijKUQ8+DYRaKnepf9spvKLZ+nrmZ+JrRcB3FrdTX/l9JDpggcUcC/Fe+A4KIZOnClfxLn6zfIlkCZHNA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/buffer-from-string@0.0.8':
- resolution: {integrity: sha512-Dws5ZbK2M9l4Bkn/ODHFm3lNZ8tWko+NYXqGS/UH/RIQv3PGp+1tXFUSvjwjDneM6ppjQVExzVedUH1ftABs9A==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/cli-ctor@0.0.3':
- resolution: {integrity: sha512-0zCuZnzFyxj66GoF8AyIOhTX5/mgGczFvr6T9h4mXwegMZp8jBC/ZkOGMwmp+ODLBTvlcnnDNpNFkDDyR6/c2g==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/complex-float32@0.0.7':
- resolution: {integrity: sha512-POCtQcBZnPm4IrFmTujSaprR1fcOFr/MRw2Mt7INF4oed6b1nzeG647K+2tk1m4mMrMPiuXCdvwJod4kJ0SXxQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/complex-float64@0.0.8':
- resolution: {integrity: sha512-lUJwsXtGEziOWAqCcnKnZT4fcVoRsl6t6ECaCJX45Z7lAc70yJLiwUieLWS5UXmyoADHuZyUXkxtI4oClfpnaw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/complex-reim@0.0.6':
- resolution: {integrity: sha512-28WXfPSIFMtHb0YgdatkGS4yxX5sPYea5MiNgqPv3E78+tFcg8JJG52NQ/MviWP2wsN9aBQAoCPeu8kXxSPdzA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/complex-reimf@0.0.1':
- resolution: {integrity: sha512-P9zu05ZW2i68Oppp3oHelP7Tk0D7tGBL0hGl1skJppr2vY9LltuNbeYI3C96tQe/7Enw/5GyAWgxoQI4cWccQA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-exponent-bias@0.0.8':
- resolution: {integrity: sha512-IzBJQw9hYgWCki7VoC/zJxEA76Nmf8hmY+VkOWnJ8IyfgTXClgY8tfDGS1cc4l/hCOEllxGp9FRvVdn24A5tKQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-high-word-abs-mask@0.0.1':
- resolution: {integrity: sha512-1vy8SUyMHFBwqUUVaZFA7r4/E3cMMRKSwsaa/EZ15w7Kmc01W/ZmaaTLevRcIdACcNgK+8i8813c8H7LScXNcQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-high-word-exponent-mask@0.0.8':
- resolution: {integrity: sha512-z28/EQERc0VG7N36bqdvtrRWjFc8600PKkwvl/nqx6TpKAzMXNw55BS1xT4C28Sa9Z7uBWeUj3UbIFedbkoyMw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-high-word-sign-mask@0.0.1':
- resolution: {integrity: sha512-hmTr5caK1lh1m0eyaQqt2Vt3y+eEdAx57ndbADEbXhxC9qSGd0b4bLSzt/Xp4MYBYdQkHAE/BlkgUiRThswhCg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-max-base2-exponent-subnormal@0.0.8':
- resolution: {integrity: sha512-YGBZykSiXFebznnJfWFDwhho2Q9xhUWOL+X0lZJ4ItfTTo40W6VHAyNYz98tT/gJECFype0seNzzo1nUxCE7jQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-max-base2-exponent@0.0.8':
- resolution: {integrity: sha512-xBAOtso1eiy27GnTut2difuSdpsGxI8dJhXupw0UukGgvy/3CSsyNm+a1Suz/dhqK4tPOTe5QboIdNMw5IgXKQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-min-base2-exponent-subnormal@0.0.8':
- resolution: {integrity: sha512-bt81nBus/91aEqGRQBenEFCyWNsf8uaxn4LN1NjgkvY92S1yVxXFlC65fJHsj9FTqvyZ+uj690/gdMKUDV3NjQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-ninf@0.0.8':
- resolution: {integrity: sha512-bn/uuzCne35OSLsQZJlNrkvU1/40spGTm22g1+ZI1LL19J8XJi/o4iupIHRXuLSTLFDBqMoJlUNphZlWQ4l8zw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-pinf@0.0.8':
- resolution: {integrity: sha512-I3R4rm2cemoMuiDph07eo5oWZ4ucUtpuK73qBJiJPDQKz8fSjSe4wJBAigq2AmWYdd7yJHsl5NJd8AgC6mP5Qw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-float64-smallest-normal@0.0.8':
- resolution: {integrity: sha512-Qwxpn5NA3RXf+mQcffCWRcsHSPTUQkalsz0+JDpblDszuz2XROcXkOdDr5LKgTAUPIXsjOgZzTsuRONENhsSEg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-uint16-max@0.0.7':
- resolution: {integrity: sha512-7TPoku7SlskA67mAm7mykIAjeEnkQJemw1cnKZur0mT5W4ryvDR6iFfL9xBiByVnWYq/+ei7DHbOv6/2b2jizw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-uint32-max@0.0.7':
- resolution: {integrity: sha512-8+NK0ewqc1vnEZNqzwFJgFSy3S543Eft7i8WyW/ygkofiqEiLAsujvYMHzPAB8/3D+PYvjTSe37StSwRwvQ6uw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/constants-uint8-max@0.0.7':
- resolution: {integrity: sha512-fqV+xds4jgwFxwWu08b8xDuIoW6/D4/1dtEjZ1sXVeWR7nf0pjj1cHERq4kdkYxsvOGu+rjoR3MbjzpFc4fvSw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/fs-exists@0.0.8':
- resolution: {integrity: sha512-mZktcCxiLmycCJefm1+jbMTYkmhK6Jk1ShFmUVqJvs+Ps9/2EEQXfPbdEniLoVz4HeHLlcX90JWobUEghOOnAQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/fs-read-file@0.0.8':
- resolution: {integrity: sha512-pIZID/G91+q7ep4x9ECNC45+JT2j0+jdz/ZQVjCHiEwXCwshZPEvxcPQWb9bXo6coOY+zJyX5TwBIpXBxomWFg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/fs-resolve-parent-path@0.0.8':
- resolution: {integrity: sha512-ok1bTWsAziChibQE3u7EoXwbCQUDkFjjRAHSxh7WWE5JEYVJQg1F0o3bbjRr4D/wfYYPWLAt8AFIKBUDmWghpg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/math-base-assert-is-infinite@0.0.9':
- resolution: {integrity: sha512-JuPDdmxd+AtPWPHu9uaLvTsnEPaZODZk+zpagziNbDKy8DRiU1cy+t+QEjB5WizZt0A5MkuxDTjZ/8/sG5GaYQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-assert-is-nan@0.0.8':
- resolution: {integrity: sha512-m+gCVBxLFW8ZdAfdkATetYMvM7sPFoMKboacHjb1pe21jHQqVb+/4bhRSDg6S7HGX7/8/bSzEUm9zuF7vqK5rQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-napi-binary@0.0.8':
- resolution: {integrity: sha512-B8d0HBPhfXefbdl/h0h5c+lM2sE+/U7Fb7hY/huVeoQtBtEx0Jbx/qKvPSVxMjmWCKfWlbPpbgKpN5GbFgLiAg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-napi-unary@0.0.8':
- resolution: {integrity: sha512-xKbGBxbgrEe7dxCDXJrooXPhXSDUl/QPqsN74Qa0+8Svsc4sbYVdU3yHSN5vDgrcWt3ZkH51j0vCSBIjvLL15g==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-special-abs@0.0.6':
- resolution: {integrity: sha512-FaaMUnYs2qIVN3kI5m/qNlBhDnjszhDOzEhxGEoQWR/k0XnxbCsTyjNesR2DkpiKuoAXAr9ojoDe2qBYdirWoQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-special-copysign@0.0.7':
- resolution: {integrity: sha512-7Br7oeuVJSBKG8BiSk/AIRFTBd2sbvHdV3HaqRj8tTZHX8BQomZ3Vj4Qsiz3kPyO4d6PpBLBTYlGTkSDlGOZJA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/math-base-special-ldexp@0.0.5':
- resolution: {integrity: sha512-RLRsPpCdcJZMhwb4l4B/FsmGfEPEWAsik6KYUkUSSHb7ok/gZWt8LgVScxGMpJMpl5IV0v9qG4ZINVONKjX5KA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-ctor@0.0.7':
- resolution: {integrity: sha512-kXNwKIfnb10Ro3RTclhAYqbE3DtIXax+qpu0z1/tZpI2vkmTfYDQLno2QJrzJsZZgdeFtXIws+edONN9kM34ow==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-exponent@0.0.6':
- resolution: {integrity: sha512-wLXsG+cvynmapoffmj5hVNDH7BuHIGspBcTCdjPaD+tnqPDIm03qV5Z9YBhDh91BdOCuPZQ8Ovu2WBpX+ySeGg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-from-words@0.0.6':
- resolution: {integrity: sha512-r0elnekypCN831aw9Gp8+08br8HHAqvqtc5uXaxEh3QYIgBD/QM5qSb3b7WSAQ0ZxJJKdoykupODWWBkWQTijg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-get-high-word@0.0.6':
- resolution: {integrity: sha512-jSFSYkgiG/IzDurbwrDKtWiaZeSEJK8iJIsNtbPG1vOIdQMRyw+t0bf3Kf3vuJu/+bnSTvYZLqpCO6wzT/ve9g==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-normalize@0.0.9':
- resolution: {integrity: sha512-+rm7RQJEj8zHkqYFE2a6DgNQSB5oKE/IydHAajgZl40YB91BoYRYf/ozs5/tTwfy2Fc04+tIpSfFtzDr4ZY19Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-to-float32@0.0.7':
- resolution: {integrity: sha512-PNUSi6+cqfFiu4vgFljUKMFY2O9PxI6+T+vqtIoh8cflf+PjSGj3v4QIlstK9+6qU40eGR5SHZyLTWdzmNqLTQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/number-float64-base-to-words@0.0.7':
- resolution: {integrity: sha512-7wsYuq+2MGp9rAkTnQ985rah7EJI9TfgHrYSSd4UIu4qIjoYmWIKEhIDgu7/69PfGrls18C3PxKg1pD/v7DQTg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/os-byte-order@0.0.7':
- resolution: {integrity: sha512-rRJWjFM9lOSBiIX4zcay7BZsqYBLoE32Oz/Qfim8cv1cN1viS5D4d3DskRJcffw7zXDnG3oZAOw5yZS0FnlyUg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/os-float-word-order@0.0.7':
- resolution: {integrity: sha512-gXIcIZf+ENKP7E41bKflfXmPi+AIfjXW/oU+m8NbP3DQasqHaZa0z5758qvnbO8L1lRJb/MzLOkIY8Bx/0cWEA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/process-cwd@0.0.8':
- resolution: {integrity: sha512-GHINpJgSlKEo9ODDWTHp0/Zc/9C/qL92h5Mc0QlIFBXAoUjy6xT4FB2U16wCNZMG3eVOzt5+SjmCwvGH0Wbg3Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/process-read-stdin@0.0.7':
- resolution: {integrity: sha512-nep9QZ5iDGrRtrZM2+pYAvyCiYG4HfO0/9+19BiLJepjgYq4GKeumPAQo22+1xawYDL7Zu62uWzYszaVZcXuyw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/regexp-eol@0.0.7':
- resolution: {integrity: sha512-BTMpRWrmlnf1XCdTxOrb8o6caO2lmu/c80XSyhYCi1DoizVIZnqxOaN5yUJNCr50g28vQ47PpsT3Yo7J3SdlRA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/regexp-extended-length-path@0.0.7':
- resolution: {integrity: sha512-z6uqzMWq3WPDKbl4MIZJoNA5ZsYLQI9G3j2TIvhU8X2hnhlku8p4mvK9F+QmoVvgPxKliwNnx/DAl7ltutSDKw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/regexp-function-name@0.0.7':
- resolution: {integrity: sha512-MaiyFUUqkAUpUoz/9F6AMBuMQQfA9ssQfK16PugehLQh4ZtOXV1LhdY8e5Md7SuYl9IrvFVg1gSAVDysrv5ZMg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/regexp-regexp@0.0.8':
- resolution: {integrity: sha512-S5PZICPd/XRcn1dncVojxIDzJsHtEleuJHHD7ji3o981uPHR7zI2Iy9a1eV2u7+ABeUswbI1Yuix6fXJfcwV1w==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/streams-node-stdin@0.0.7':
- resolution: {integrity: sha512-gg4lgrjuoG3V/L29wNs32uADMCqepIcmoOFHJCTAhVe0GtHDLybUVnLljaPfdvmpPZmTvmusPQtIcscbyWvAyg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/string-base-format-interpolate@0.0.4':
- resolution: {integrity: sha512-8FC8+/ey+P5hf1B50oXpXzRzoAgKI1rikpyKZ98Xmjd5rcbSq3NWYi8TqOF8mUHm9hVZ2CXWoNCtEe2wvMQPMg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/string-base-format-tokenize@0.0.4':
- resolution: {integrity: sha512-+vMIkheqAhDeT/iF5hIQo95IMkt5IzC68eR3CxW1fhc48NMkKFE2UfN73ET8fmLuOanLo/5pO2E90c2G7PExow==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/string-format@0.0.3':
- resolution: {integrity: sha512-1jiElUQXlI/tTkgRuzJi9jUz/EjrO9kzS8VWHD3g7gdc3ZpxlA5G9JrIiPXGw/qmZTi0H1pXl6KmX+xWQEQJAg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/string-lowercase@0.0.9':
- resolution: {integrity: sha512-tXFFjbhIlDak4jbQyV1DhYiSTO8b1ozS2g/LELnsKUjIXECDKxGFyWYcz10KuyAWmFotHnCJdIm8/blm2CfDIA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/string-replace@0.0.11':
- resolution: {integrity: sha512-F0MY4f9mRE5MSKpAUfL4HLbJMCbG6iUTtHAWnNeAXIvUX1XYIw/eItkA58R9kNvnr1l5B08bavnjrgTJGIKFFQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/types@0.0.14':
- resolution: {integrity: sha512-AP3EI9/il/xkwUazcoY+SbjtxHRrheXgSbWZdEGD+rWpEgj6n2i63hp6hTOpAB5NipE0tJwinQlDGOuQ1lCaCw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-constructor-name@0.0.8':
- resolution: {integrity: sha512-GXpyNZwjN8u3tyYjL2GgGfrsxwvfogUC3gg7L7NRZ1i86B6xmgfnJUYHYOUnSfB+R531ET7NUZlK52GxL7P82Q==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-convert-path@0.0.8':
- resolution: {integrity: sha512-GNd8uIswrcJCctljMbmjtE4P4oOjhoUIfMvdkqfSrRLRY+ZqPB2xM+yI0MQFfUq/0Rnk/xtESlGSVLz9ZDtXfA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/utils-define-nonenumerable-read-only-property@0.0.7':
- resolution: {integrity: sha512-c7dnHDYuS4Xn3XBRWIQBPcROTtP/4lkcFyq0FrQzjXUjimfMgHF7cuFIIob6qUTnU8SOzY9p0ydRR2QJreWE6g==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-define-property@0.0.9':
- resolution: {integrity: sha512-pIzVvHJvVfU/Lt45WwUAcodlvSPDDSD4pIPc9WmIYi4vnEBA9U7yHtiNz2aTvfGmBMTaLYTVVFIXwkFp+QotMA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-escape-regexp-string@0.0.9':
- resolution: {integrity: sha512-E+9+UDzf2mlMLgb+zYrrPy2FpzbXh189dzBJY6OG+XZqEJAXcjWs7DURO5oGffkG39EG5KXeaQwDXUavcMDCIw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-get-prototype-of@0.0.7':
- resolution: {integrity: sha512-fCUk9lrBO2ELrq+/OPJws1/hquI4FtwG0SzVRH6UJmJfwb1zoEFnjcwyDAy+HWNVmo3xeRLsrz6XjHrJwer9pg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-global@0.0.7':
- resolution: {integrity: sha512-BBNYBdDUz1X8Lhfw9nnnXczMv9GztzGpQ88J/6hnY7PHJ71av5d41YlijWeM9dhvWjnH9I7HNE3LL7R07yw0kA==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-library-manifest@0.0.8':
- resolution: {integrity: sha512-IOQSp8skSRQn9wOyMRUX9Hi0j/P5v5TvD8DJWTqtE8Lhr8kVVluMBjHfvheoeKHxfWAbNHSVpkpFY/Bdh/SHgQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
- hasBin: true
-
- '@stdlib/utils-native-class@0.0.8':
- resolution: {integrity: sha512-0Zl9me2V9rSrBw/N8o8/9XjmPUy8zEeoMM0sJmH3N6C9StDsYTjXIAMPGzYhMEWaWHvGeYyNteFK2yDOVGtC3w==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-next-tick@0.0.8':
- resolution: {integrity: sha512-l+hPl7+CgLPxk/gcWOXRxX/lNyfqcFCqhzzV/ZMvFCYLY/wI9lcWO4xTQNMALY2rp+kiV+qiAiO9zcO+hewwUg==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-noop@0.0.13':
- resolution: {integrity: sha512-JRWHGWYWP5QK7SQ2cOYiL8NETw8P33LriZh1p9S2xC4e0rBoaY849h1A2IL2y1+x3s29KNjSaBWMrMUIV5HCSw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-regexp-from-string@0.0.9':
- resolution: {integrity: sha512-3rN0Mcyiarl7V6dXRjFAUMacRwe0/sYX7ThKYurf0mZkMW9tjTP+ygak9xmL9AL0QQZtbrFFwWBrDO+38Vnavw==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
- '@stdlib/utils-type-of@0.0.8':
- resolution: {integrity: sha512-b4xqdy3AnnB7NdmBBpoiI67X4vIRxvirjg3a8BfhM5jPr2k0njby1jAbG9dUxJvgAV6o32S4kjUgfIdjEYpTNQ==}
- engines: {node: '>=0.10.0', npm: '>2.7.0'}
- os: [aix, darwin, freebsd, linux, macos, openbsd, sunos, win32, windows]
-
'@tailwindcss/typography@0.5.15':
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
peerDependencies:
@@ -2212,6 +1677,10 @@ packages:
resolution: {integrity: sha512-Rircqi9ch8AnZscQcsA1C47NFdaO3wukpmIRzYcDOrmvgt78hM/sj5pZhZNec2NM12uk5vTwRHZ4anGcrC4ZTg==}
engines: {node: '>=16'}
+ camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+
camelcase@8.0.0:
resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==}
engines: {node: '>=16'}
@@ -2280,6 +1749,9 @@ packages:
resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ cliui@6.0.0:
+ resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -2484,6 +1956,10 @@ packages:
supports-color:
optional: true
+ decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+
decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
@@ -2519,6 +1995,9 @@ packages:
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dijkstrajs@1.0.3:
+ resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -2561,10 +2040,6 @@ packages:
dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
- dset@3.1.4:
- resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==}
- engines: {node: '>=4'}
-
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -2866,6 +2341,9 @@ packages:
fastq@1.15.0:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
+ fflate@0.4.8:
+ resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
+
file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
@@ -2882,6 +2360,10 @@ packages:
resolution: {integrity: sha512-OuWNfjfP05JcpAP3JPgAKUhWefjMRfI5iAoSsvE24ANYWJaepAtlSgWECSVEuRgSXpyNEc9DJwG/TZpgcOqyig==}
engines: {node: '>=16'}
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -3382,10 +2864,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
- js-cookie@3.0.1:
- resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
- engines: {node: '>=12'}
-
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -3520,6 +2998,10 @@ packages:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -3743,21 +3225,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- new-date@1.0.3:
- resolution: {integrity: sha512-0fsVvQPbo2I18DT2zVHpezmeeNYV2JaJSrseiHLc17GNOxJzUdx5mvSigPu8LtIfZSij5i1wXnXFspEs2CD6hA==}
-
no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
- node-fetch@2.6.11:
- resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
- engines: {node: 4.x || >=6.0.0}
- peerDependencies:
- encoding: ^0.1.0
- peerDependenciesMeta:
- encoding:
- optional: true
-
node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
@@ -3787,9 +3257,6 @@ packages:
nwsapi@2.2.12:
resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==}
- obj-case@0.2.1:
- resolution: {integrity: sha512-PquYBBTy+Y6Ob/O2574XHhDtHJlV1cJHMCgW+rDRc9J5hhmRelJB3k5dTK/3cVmFVtzvAKuENeuLpoyTzMzkOg==}
-
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -3866,6 +3333,10 @@ packages:
orderedmap@2.1.0:
resolution: {integrity: sha512-/pIFexOm6S70EPdznemIz3BQZoJ4VTFrhqzu0ACBqBgeLsLxq8e6Jim63ImIfwW/zAD1AlXpRMlOv3aghmo4dA==}
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -3874,6 +3345,10 @@ packages:
resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
p-locate@5.0.0:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
@@ -3882,6 +3357,10 @@ packages:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
package-json-from-dist@1.0.0:
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
@@ -3986,6 +3465,10 @@ packages:
pkg-types@1.2.0:
resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==}
+ pngjs@5.0.0:
+ resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+ engines: {node: '>=10.13.0'}
+
possible-typed-array-names@1.0.0:
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
engines: {node: '>= 0.4'}
@@ -4201,6 +3684,20 @@ packages:
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
engines: {node: ^10 || ^12 || >=14}
+ posthog-js@1.260.3:
+ resolution: {integrity: sha512-FCtksk0GQn22Rk9P7x7dsmAO7a2aBxPeYb2O2KXSraxR8xd2G6lUOOthVDK+qgtmuhpUZuur/mHrXEslMUEtjg==}
+ peerDependencies:
+ '@rrweb/types': 2.0.0-alpha.17
+ rrweb-snapshot: 2.0.0-alpha.17
+ peerDependenciesMeta:
+ '@rrweb/types':
+ optional: true
+ rrweb-snapshot:
+ optional: true
+
+ preact@10.27.1:
+ resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -4283,6 +3780,11 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ qrcode@1.5.4:
+ resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
@@ -4326,6 +3828,9 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
+ require-main-filename@2.0.0:
+ resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
@@ -4435,6 +3940,9 @@ packages:
sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
+ set-blocking@2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -4661,9 +4169,6 @@ packages:
timezone-phone-codes@0.0.2:
resolution: {integrity: sha512-KRPfuCfb7nSxBJqFrUAgRzWlEG0/4bu5D/3Hvw1hvl7BLhWIxQ5F8G/qKeT04DQSIenq/jQ1cZu0xOsHjAC3Jg==}
- tiny-hashes@1.0.1:
- resolution: {integrity: sha512-knIN5zj4fl7kW4EBU5sLP20DWUvi/rVouvJezV0UAym2DkQaqm365Nyc8F3QEiOvunNDMxR8UhcXd1d5g+Wg1g==}
-
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
@@ -4697,9 +4202,6 @@ packages:
resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
engines: {node: '>=6'}
- tr46@0.0.3:
- resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
-
tr46@3.0.0:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'}
@@ -4714,9 +4216,6 @@ packages:
tsconfig-paths@3.15.0:
resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
- tslib@2.6.2:
- resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
-
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
@@ -4773,11 +4272,6 @@ packages:
resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
engines: {node: '>= 0.4'}
- typescript@4.9.5:
- resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
- engines: {node: '>=4.2.0'}
- hasBin: true
-
typescript@5.6.2:
resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
engines: {node: '>=14.17'}
@@ -4798,12 +4292,6 @@ packages:
undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
- unfetch@3.1.2:
- resolution: {integrity: sha512-L0qrK7ZeAudGiKYw6nzFjnJ2D5WHblUBwmHIqtPS6oKUd+Hcpk7/hKsSmcHsTlpd1TbTNsiRBUKRq3bHLNIqIw==}
-
- unfetch@4.2.0:
- resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==}
-
universalify@0.2.0:
resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
engines: {node: '>= 4.0.0'}
@@ -5058,8 +4546,8 @@ packages:
wavesurfer.js@7.8.6:
resolution: {integrity: sha512-EDexkMwkkQBTWruhfWQRkTtvRggtKFTPuJX/oZ5wbIZEfyww9EBeLr2mtkxzA1S8TlWPx6adY5WyjOlNYNyHSg==}
- webidl-conversions@3.0.1:
- resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ web-vitals@4.2.4:
+ resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
@@ -5093,12 +4581,12 @@ packages:
resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==}
engines: {node: '>=18'}
- whatwg-url@5.0.0:
- resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
-
which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
+ which-module@2.0.1:
+ resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+
which-typed-array@1.1.11:
resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==}
engines: {node: '>= 0.4'}
@@ -5121,6 +4609,10 @@ packages:
resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==}
engines: {node: '>=18'}
+ wrap-ansi@6.2.0:
+ resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+ engines: {node: '>=8'}
+
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -5159,6 +4651,9 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ y18n@4.0.3:
+ resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -5179,10 +4674,18 @@ packages:
engines: {node: '>= 14'}
hasBin: true
+ yargs-parser@18.1.3:
+ resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+ engines: {node: '>=6'}
+
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
+ yargs@15.4.1:
+ resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+ engines: {node: '>=8'}
+
yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
@@ -5268,7 +4771,7 @@ snapshots:
prosemirror-utils: 1.2.2(prosemirror-model@1.22.3)(prosemirror-state@1.4.3)
prosemirror-view: 1.34.1
- '@chatwoot/utils@0.0.49':
+ '@chatwoot/utils@0.0.50':
dependencies:
date-fns: 2.30.0
@@ -5893,24 +5396,6 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.4
optional: true
- '@june-so/analytics-next@2.0.0':
- dependencies:
- '@lukeed/uuid': 2.0.0
- '@segment/analytics-core': 1.2.2
- '@segment/analytics.js-video-plugins': 0.2.1
- '@segment/facade': 3.4.10
- '@segment/tsub': 1.0.1
- dset: 3.1.4
- js-cookie: 3.0.1
- node-fetch: 2.6.11
- spark-md5: 3.0.2
- tslib: 2.6.2
- typescript: 4.9.5
- unfetch: 4.2.0
- transitivePeerDependencies:
- - encoding
- - supports-color
-
'@kurkle/color@0.3.2': {}
'@lezer/common@1.2.2': {}
@@ -5937,12 +5422,6 @@ snapshots:
'@lk77/vue3-color@3.0.6': {}
- '@lukeed/csprng@1.0.1': {}
-
- '@lukeed/uuid@2.0.0':
- dependencies:
- '@lukeed/csprng': 1.0.1
-
'@material/mwc-icon@0.25.3':
dependencies:
lit: 2.2.6
@@ -5969,6 +5448,8 @@ snapshots:
'@polka/url@1.0.0-next.28': {}
+ '@posthog/core@1.0.1': {}
+
'@radix-ui/colors@3.0.0': {}
'@rails/actioncable@6.1.3': {}
@@ -6039,38 +5520,6 @@ snapshots:
'@scmmishra/pico-search@0.5.4': {}
- '@segment/analytics-core@1.2.2':
- dependencies:
- '@lukeed/uuid': 2.0.0
- dset: 3.1.4
- tslib: 2.8.1
-
- '@segment/analytics.js-video-plugins@0.2.1':
- dependencies:
- unfetch: 3.1.2
-
- '@segment/facade@3.4.10':
- dependencies:
- '@segment/isodate-traverse': 1.1.1
- inherits: 2.0.4
- new-date: 1.0.3
- obj-case: 0.2.1
-
- '@segment/isodate-traverse@1.1.1':
- dependencies:
- '@segment/isodate': 1.0.3
-
- '@segment/isodate@1.0.3': {}
-
- '@segment/tsub@1.0.1':
- dependencies:
- '@stdlib/math-base-special-ldexp': 0.0.5
- dlv: 1.1.3
- dset: 3.1.4
- tiny-hashes: 1.0.1
- transitivePeerDependencies:
- - supports-color
-
'@sentry-internal/browser-utils@8.31.0':
dependencies:
'@sentry/core': 8.31.0
@@ -6140,624 +5589,6 @@ snapshots:
semver: 7.5.3
size-limit: 8.2.6
- '@stdlib/array-float32@0.0.6':
- dependencies:
- '@stdlib/assert-has-float32array-support': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/array-float64@0.0.6':
- dependencies:
- '@stdlib/assert-has-float64array-support': 0.0.8
-
- '@stdlib/array-uint16@0.0.6':
- dependencies:
- '@stdlib/assert-has-uint16array-support': 0.0.8
-
- '@stdlib/array-uint32@0.0.6':
- dependencies:
- '@stdlib/assert-has-uint32array-support': 0.0.8
-
- '@stdlib/array-uint8@0.0.7':
- dependencies:
- '@stdlib/assert-has-uint8array-support': 0.0.8
-
- '@stdlib/assert-has-float32array-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-float32array': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/constants-float64-pinf': 0.0.8
- '@stdlib/fs-read-file': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/assert-has-float64array-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-float64array': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-node-buffer-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-buffer': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-own-property@0.0.7': {}
-
- '@stdlib/assert-has-symbol-support@0.0.8':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-tostringtag-support@0.0.9':
- dependencies:
- '@stdlib/assert-has-symbol-support': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-uint16array-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-uint16array': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/constants-uint16-max': 0.0.7
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-uint32array-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-uint32array': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/constants-uint32-max': 0.0.7
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-has-uint8array-support@0.0.8':
- dependencies:
- '@stdlib/assert-is-uint8array': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/constants-uint8-max': 0.0.7
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-is-array@0.0.7':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-big-endian@0.0.7':
- dependencies:
- '@stdlib/array-uint16': 0.0.6
- '@stdlib/array-uint8': 0.0.7
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-is-boolean@0.0.8':
- dependencies:
- '@stdlib/assert-has-tostringtag-support': 0.0.9
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-buffer@0.0.8':
- dependencies:
- '@stdlib/assert-is-object-like': 0.0.8
-
- '@stdlib/assert-is-float32array@0.0.8':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-float64array@0.0.8':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-function@0.0.8':
- dependencies:
- '@stdlib/utils-type-of': 0.0.8
-
- '@stdlib/assert-is-little-endian@0.0.7':
- dependencies:
- '@stdlib/array-uint16': 0.0.6
- '@stdlib/array-uint8': 0.0.7
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/assert-is-number@0.0.7':
- dependencies:
- '@stdlib/assert-has-tostringtag-support': 0.0.9
- '@stdlib/number-ctor': 0.0.7
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-object-like@0.0.8':
- dependencies:
- '@stdlib/assert-tools-array-function': 0.0.7
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/assert-is-object@0.0.8':
- dependencies:
- '@stdlib/assert-is-array': 0.0.7
-
- '@stdlib/assert-is-plain-object@0.0.7':
- dependencies:
- '@stdlib/assert-has-own-property': 0.0.7
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/assert-is-object': 0.0.8
- '@stdlib/utils-get-prototype-of': 0.0.7
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-regexp-string@0.0.9':
- dependencies:
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-read-stdin': 0.0.7
- '@stdlib/regexp-eol': 0.0.7
- '@stdlib/regexp-regexp': 0.0.8
- '@stdlib/streams-node-stdin': 0.0.7
-
- '@stdlib/assert-is-regexp@0.0.7':
- dependencies:
- '@stdlib/assert-has-tostringtag-support': 0.0.9
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-string@0.0.8':
- dependencies:
- '@stdlib/assert-has-tostringtag-support': 0.0.9
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-uint16array@0.0.8':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-uint32array@0.0.8':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-is-uint8array@0.0.8':
- dependencies:
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/assert-tools-array-function@0.0.7':
- dependencies:
- '@stdlib/assert-is-array': 0.0.7
-
- '@stdlib/buffer-ctor@0.0.7':
- dependencies:
- '@stdlib/assert-has-node-buffer-support': 0.0.8
-
- '@stdlib/buffer-from-string@0.0.8':
- dependencies:
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/buffer-ctor': 0.0.7
- '@stdlib/string-format': 0.0.3
-
- '@stdlib/cli-ctor@0.0.3':
- dependencies:
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-noop': 0.0.13
- minimist: 1.2.8
-
- '@stdlib/complex-float32@0.0.7':
- dependencies:
- '@stdlib/assert-is-number': 0.0.7
- '@stdlib/number-float64-base-to-float32': 0.0.7
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-define-property': 0.0.9
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/complex-float64@0.0.8':
- dependencies:
- '@stdlib/assert-is-number': 0.0.7
- '@stdlib/complex-float32': 0.0.7
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-define-property': 0.0.9
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/complex-reim@0.0.6':
- dependencies:
- '@stdlib/array-float64': 0.0.6
- '@stdlib/complex-float64': 0.0.8
- '@stdlib/types': 0.0.14
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/complex-reimf@0.0.1':
- dependencies:
- '@stdlib/array-float32': 0.0.6
- '@stdlib/complex-float32': 0.0.7
- '@stdlib/types': 0.0.14
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-exponent-bias@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-high-word-abs-mask@0.0.1':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-high-word-exponent-mask@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-high-word-sign-mask@0.0.1':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-max-base2-exponent-subnormal@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-max-base2-exponent@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-min-base2-exponent-subnormal@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-ninf@0.0.8':
- dependencies:
- '@stdlib/number-ctor': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-pinf@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-float64-smallest-normal@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/constants-uint16-max@0.0.7': {}
-
- '@stdlib/constants-uint32-max@0.0.7': {}
-
- '@stdlib/constants-uint8-max@0.0.7': {}
-
- '@stdlib/fs-exists@0.0.8':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-cwd': 0.0.8
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/fs-read-file@0.0.8':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/fs-resolve-parent-path@0.0.8':
- dependencies:
- '@stdlib/assert-has-own-property': 0.0.7
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/assert-is-plain-object': 0.0.7
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-exists': 0.0.8
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-cwd': 0.0.8
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/math-base-assert-is-infinite@0.0.9':
- dependencies:
- '@stdlib/constants-float64-ninf': 0.0.8
- '@stdlib/constants-float64-pinf': 0.0.8
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-assert-is-nan@0.0.8':
- dependencies:
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-napi-binary@0.0.8':
- dependencies:
- '@stdlib/complex-float32': 0.0.7
- '@stdlib/complex-float64': 0.0.8
- '@stdlib/complex-reim': 0.0.6
- '@stdlib/complex-reimf': 0.0.1
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-napi-unary@0.0.8':
- dependencies:
- '@stdlib/complex-float32': 0.0.7
- '@stdlib/complex-float64': 0.0.8
- '@stdlib/complex-reim': 0.0.6
- '@stdlib/complex-reimf': 0.0.1
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-special-abs@0.0.6':
- dependencies:
- '@stdlib/math-base-napi-unary': 0.0.8
- '@stdlib/number-float64-base-to-words': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-special-copysign@0.0.7':
- dependencies:
- '@stdlib/constants-float64-high-word-abs-mask': 0.0.1
- '@stdlib/constants-float64-high-word-sign-mask': 0.0.1
- '@stdlib/math-base-napi-binary': 0.0.8
- '@stdlib/number-float64-base-from-words': 0.0.6
- '@stdlib/number-float64-base-get-high-word': 0.0.6
- '@stdlib/number-float64-base-to-words': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/math-base-special-ldexp@0.0.5':
- dependencies:
- '@stdlib/constants-float64-exponent-bias': 0.0.8
- '@stdlib/constants-float64-max-base2-exponent': 0.0.8
- '@stdlib/constants-float64-max-base2-exponent-subnormal': 0.0.8
- '@stdlib/constants-float64-min-base2-exponent-subnormal': 0.0.8
- '@stdlib/constants-float64-ninf': 0.0.8
- '@stdlib/constants-float64-pinf': 0.0.8
- '@stdlib/math-base-assert-is-infinite': 0.0.9
- '@stdlib/math-base-assert-is-nan': 0.0.8
- '@stdlib/math-base-special-copysign': 0.0.7
- '@stdlib/number-float64-base-exponent': 0.0.6
- '@stdlib/number-float64-base-from-words': 0.0.6
- '@stdlib/number-float64-base-normalize': 0.0.9
- '@stdlib/number-float64-base-to-words': 0.0.7
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-ctor@0.0.7': {}
-
- '@stdlib/number-float64-base-exponent@0.0.6':
- dependencies:
- '@stdlib/constants-float64-exponent-bias': 0.0.8
- '@stdlib/constants-float64-high-word-exponent-mask': 0.0.8
- '@stdlib/number-float64-base-get-high-word': 0.0.6
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-float64-base-from-words@0.0.6':
- dependencies:
- '@stdlib/array-float64': 0.0.6
- '@stdlib/array-uint32': 0.0.6
- '@stdlib/assert-is-little-endian': 0.0.7
- '@stdlib/number-float64-base-to-words': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-float64-base-get-high-word@0.0.6':
- dependencies:
- '@stdlib/array-float64': 0.0.6
- '@stdlib/array-uint32': 0.0.6
- '@stdlib/assert-is-little-endian': 0.0.7
- '@stdlib/number-float64-base-to-words': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-float64-base-normalize@0.0.9':
- dependencies:
- '@stdlib/constants-float64-smallest-normal': 0.0.8
- '@stdlib/math-base-assert-is-infinite': 0.0.9
- '@stdlib/math-base-assert-is-nan': 0.0.8
- '@stdlib/math-base-special-abs': 0.0.6
- '@stdlib/types': 0.0.14
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-float64-base-to-float32@0.0.7':
- dependencies:
- '@stdlib/array-float32': 0.0.6
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/number-float64-base-to-words@0.0.7':
- dependencies:
- '@stdlib/array-float64': 0.0.6
- '@stdlib/array-uint32': 0.0.6
- '@stdlib/assert-is-little-endian': 0.0.7
- '@stdlib/os-byte-order': 0.0.7
- '@stdlib/os-float-word-order': 0.0.7
- '@stdlib/types': 0.0.14
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/os-byte-order@0.0.7':
- dependencies:
- '@stdlib/assert-is-big-endian': 0.0.7
- '@stdlib/assert-is-little-endian': 0.0.7
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/os-float-word-order@0.0.7':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/os-byte-order': 0.0.7
- '@stdlib/utils-library-manifest': 0.0.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/process-cwd@0.0.8':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
-
- '@stdlib/process-read-stdin@0.0.7':
- dependencies:
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/buffer-ctor': 0.0.7
- '@stdlib/buffer-from-string': 0.0.8
- '@stdlib/streams-node-stdin': 0.0.7
- '@stdlib/utils-next-tick': 0.0.8
-
- '@stdlib/regexp-eol@0.0.7':
- dependencies:
- '@stdlib/assert-has-own-property': 0.0.7
- '@stdlib/assert-is-boolean': 0.0.8
- '@stdlib/assert-is-plain-object': 0.0.7
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/regexp-extended-length-path@0.0.7':
- dependencies:
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/regexp-function-name@0.0.7':
- dependencies:
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/regexp-regexp@0.0.8':
- dependencies:
- '@stdlib/utils-define-nonenumerable-read-only-property': 0.0.7
-
- '@stdlib/streams-node-stdin@0.0.7': {}
-
- '@stdlib/string-base-format-interpolate@0.0.4': {}
-
- '@stdlib/string-base-format-tokenize@0.0.4': {}
-
- '@stdlib/string-format@0.0.3':
- dependencies:
- '@stdlib/string-base-format-interpolate': 0.0.4
- '@stdlib/string-base-format-tokenize': 0.0.4
-
- '@stdlib/string-lowercase@0.0.9':
- dependencies:
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-read-stdin': 0.0.7
- '@stdlib/streams-node-stdin': 0.0.7
- '@stdlib/string-format': 0.0.3
-
- '@stdlib/string-replace@0.0.11':
- dependencies:
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/assert-is-regexp': 0.0.7
- '@stdlib/assert-is-regexp-string': 0.0.9
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-read-stdin': 0.0.7
- '@stdlib/regexp-eol': 0.0.7
- '@stdlib/streams-node-stdin': 0.0.7
- '@stdlib/string-format': 0.0.3
- '@stdlib/utils-escape-regexp-string': 0.0.9
- '@stdlib/utils-regexp-from-string': 0.0.9
-
- '@stdlib/types@0.0.14': {}
-
- '@stdlib/utils-constructor-name@0.0.8':
- dependencies:
- '@stdlib/assert-is-buffer': 0.0.8
- '@stdlib/regexp-function-name': 0.0.7
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/utils-convert-path@0.0.8':
- dependencies:
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-read-file': 0.0.8
- '@stdlib/process-read-stdin': 0.0.7
- '@stdlib/regexp-eol': 0.0.7
- '@stdlib/regexp-extended-length-path': 0.0.7
- '@stdlib/streams-node-stdin': 0.0.7
- '@stdlib/string-lowercase': 0.0.9
- '@stdlib/string-replace': 0.0.11
-
- '@stdlib/utils-define-nonenumerable-read-only-property@0.0.7':
- dependencies:
- '@stdlib/types': 0.0.14
- '@stdlib/utils-define-property': 0.0.9
-
- '@stdlib/utils-define-property@0.0.9':
- dependencies:
- '@stdlib/types': 0.0.14
-
- '@stdlib/utils-escape-regexp-string@0.0.9':
- dependencies:
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/string-format': 0.0.3
-
- '@stdlib/utils-get-prototype-of@0.0.7':
- dependencies:
- '@stdlib/assert-is-function': 0.0.8
- '@stdlib/utils-native-class': 0.0.8
-
- '@stdlib/utils-global@0.0.7':
- dependencies:
- '@stdlib/assert-is-boolean': 0.0.8
-
- '@stdlib/utils-library-manifest@0.0.8':
- dependencies:
- '@stdlib/cli-ctor': 0.0.3
- '@stdlib/fs-resolve-parent-path': 0.0.8
- '@stdlib/utils-convert-path': 0.0.8
- debug: 2.6.9
- resolve: 1.22.8
- transitivePeerDependencies:
- - supports-color
-
- '@stdlib/utils-native-class@0.0.8':
- dependencies:
- '@stdlib/assert-has-own-property': 0.0.7
- '@stdlib/assert-has-tostringtag-support': 0.0.9
-
- '@stdlib/utils-next-tick@0.0.8': {}
-
- '@stdlib/utils-noop@0.0.13': {}
-
- '@stdlib/utils-regexp-from-string@0.0.9':
- dependencies:
- '@stdlib/assert-is-string': 0.0.8
- '@stdlib/regexp-regexp': 0.0.8
- '@stdlib/string-format': 0.0.3
-
- '@stdlib/utils-type-of@0.0.8':
- dependencies:
- '@stdlib/utils-constructor-name': 0.0.8
- '@stdlib/utils-global': 0.0.7
-
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)':
dependencies:
lodash.castarray: 4.4.0
@@ -7366,6 +6197,8 @@ snapshots:
quick-lru: 6.1.2
type-fest: 4.26.1
+ camelcase@5.3.1: {}
+
camelcase@8.0.0: {}
caniuse-lite@1.0.30001651: {}
@@ -7460,6 +6293,12 @@ snapshots:
slice-ansi: 5.0.0
string-width: 5.1.2
+ cliui@6.0.0:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 6.2.0
+
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -7632,6 +6471,8 @@ snapshots:
dependencies:
ms: 2.1.3
+ decamelize@1.2.0: {}
+
decimal.js@10.4.3: {}
deep-eql@5.0.2: {}
@@ -7663,6 +6504,8 @@ snapshots:
didyoumean@1.2.2: {}
+ dijkstrajs@1.0.3: {}
+
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@@ -7710,8 +6553,6 @@ snapshots:
no-case: 3.0.4
tslib: 2.8.1
- dset@3.1.4: {}
-
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -8155,6 +6996,8 @@ snapshots:
dependencies:
reusify: 1.0.4
+ fflate@0.4.8: {}
+
file-entry-cache@6.0.1:
dependencies:
flat-cache: 3.1.0
@@ -8180,6 +7023,11 @@ snapshots:
common-path-prefix: 3.0.0
pkg-dir: 7.0.0
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -8730,8 +7578,6 @@ snapshots:
js-cookie: 3.0.5
nopt: 7.2.1
- js-cookie@3.0.1: {}
-
js-cookie@3.0.5: {}
js-yaml@3.14.1:
@@ -8923,6 +7769,10 @@ snapshots:
mlly: 1.7.1
pkg-types: 1.2.0
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@@ -9141,19 +7991,11 @@ snapshots:
natural-compare@1.4.0: {}
- new-date@1.0.3:
- dependencies:
- '@segment/isodate': 1.0.3
-
no-case@3.0.4:
dependencies:
lower-case: 2.0.2
tslib: 2.8.1
- node-fetch@2.6.11:
- dependencies:
- whatwg-url: 5.0.0
-
node-releases@2.0.14: {}
node-releases@2.0.18: {}
@@ -9176,8 +8018,6 @@ snapshots:
nwsapi@2.2.12: {}
- obj-case@0.2.1: {}
-
object-assign@4.1.1: {}
object-hash@3.0.0: {}
@@ -9272,6 +8112,10 @@ snapshots:
orderedmap@2.1.0: {}
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -9280,6 +8124,10 @@ snapshots:
dependencies:
yocto-queue: 1.1.1
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
p-locate@5.0.0:
dependencies:
p-limit: 3.1.0
@@ -9288,6 +8136,8 @@ snapshots:
dependencies:
p-limit: 4.0.0
+ p-try@2.2.0: {}
+
package-json-from-dist@1.0.0: {}
package-manager-detector@0.2.0: {}
@@ -9370,6 +8220,8 @@ snapshots:
mlly: 1.7.1
pathe: 1.1.2
+ pngjs@5.0.0: {}
+
possible-typed-array-names@1.0.0: {}
postcss-attribute-case-insensitive@6.0.2(postcss@8.4.47):
@@ -9624,6 +8476,16 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ posthog-js@1.260.3:
+ dependencies:
+ '@posthog/core': 1.0.1
+ core-js: 3.38.1
+ fflate: 0.4.8
+ preact: 10.27.1
+ web-vitals: 4.2.4
+
+ preact@10.27.1: {}
+
prelude-ls@1.2.1: {}
prettier-linter-helpers@1.0.0:
@@ -9731,6 +8593,12 @@ snapshots:
punycode@2.3.1: {}
+ qrcode@1.5.4:
+ dependencies:
+ dijkstrajs: 1.0.3
+ pngjs: 5.0.0
+ yargs: 15.4.1
+
querystringify@2.2.0: {}
queue-microtask@1.2.3: {}
@@ -9769,6 +8637,8 @@ snapshots:
require-from-string@2.0.2: {}
+ require-main-filename@2.0.0: {}
+
requires-port@1.0.0: {}
resolve-from@4.0.0: {}
@@ -9905,6 +8775,8 @@ snapshots:
tslib: 2.8.1
upper-case-first: 2.0.2
+ set-blocking@2.0.0: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -10179,8 +9051,6 @@ snapshots:
timezone-phone-codes@0.0.2: {}
- tiny-hashes@1.0.1: {}
-
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
@@ -10206,8 +9076,6 @@ snapshots:
universalify: 0.2.0
url-parse: 1.5.10
- tr46@0.0.3: {}
-
tr46@3.0.0:
dependencies:
punycode: 2.3.1
@@ -10225,8 +9093,6 @@ snapshots:
minimist: 1.2.8
strip-bom: 3.0.0
- tslib@2.6.2: {}
-
tslib@2.7.0: {}
tslib@2.8.1: {}
@@ -10302,8 +9168,6 @@ snapshots:
is-typed-array: 1.1.13
possible-typed-array-names: 1.0.0
- typescript@4.9.5: {}
-
typescript@5.6.2:
optional: true
@@ -10322,10 +9186,6 @@ snapshots:
undici-types@6.19.8: {}
- unfetch@3.1.2: {}
-
- unfetch@4.2.0: {}
-
universalify@0.2.0: {}
universalify@2.0.1: {}
@@ -10594,7 +9454,7 @@ snapshots:
wavesurfer.js@7.8.6: {}
- webidl-conversions@3.0.1: {}
+ web-vitals@4.2.4: {}
webidl-conversions@7.0.0: {}
@@ -10624,11 +9484,6 @@ snapshots:
tr46: 5.0.0
webidl-conversions: 7.0.0
- whatwg-url@5.0.0:
- dependencies:
- tr46: 0.0.3
- webidl-conversions: 3.0.1
-
which-boxed-primitive@1.0.2:
dependencies:
is-bigint: 1.0.4
@@ -10637,6 +9492,8 @@ snapshots:
is-string: 1.0.7
is-symbol: 1.0.4
+ which-module@2.0.1: {}
+
which-typed-array@1.1.11:
dependencies:
available-typed-arrays: 1.0.5
@@ -10666,6 +9523,12 @@ snapshots:
dependencies:
string-width: 7.2.0
+ wrap-ansi@6.2.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -10694,6 +9557,8 @@ snapshots:
xmlchars@2.2.0: {}
+ y18n@4.0.3: {}
+
y18n@5.0.8: {}
yallist@4.0.0: {}
@@ -10708,8 +9573,27 @@ snapshots:
yaml@2.5.1: {}
+ yargs-parser@18.1.3:
+ dependencies:
+ camelcase: 5.3.1
+ decamelize: 1.2.0
+
yargs-parser@21.1.1: {}
+ yargs@15.4.1:
+ dependencies:
+ cliui: 6.0.0
+ decamelize: 1.2.0
+ find-up: 4.1.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ require-main-filename: 2.0.0
+ set-blocking: 2.0.0
+ string-width: 4.2.3
+ which-module: 2.0.1
+ y18n: 4.0.3
+ yargs-parser: 18.1.3
+
yargs@17.7.2:
dependencies:
cliui: 8.0.1
diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb
new file mode 100644
index 000000000..4f80456d9
--- /dev/null
+++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb
@@ -0,0 +1,80 @@
+require 'rails_helper'
+
+RSpec.describe 'Agent Capacity Policy Inbox Limits API', type: :request do
+ let(:account) { create(:account) }
+ let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
+ let!(:inbox) { create(:inbox, account: account) }
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits' do
+ context 'when not admin' do
+ it 'requires admin role' do
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
+ params: { inbox_id: inbox.id, conversation_limit: 10 },
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when admin' do
+ it 'creates an inbox limit' do
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
+ params: { inbox_id: inbox.id, conversation_limit: 10 },
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ json_response = response.parsed_body
+ expect(json_response['conversation_limit']).to eq(10)
+ expect(json_response['inbox_id']).to eq(inbox.id)
+ end
+
+ it 'prevents duplicate inbox assignments' do
+ create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox)
+
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
+ params: { inbox_id: inbox.id, conversation_limit: 10 },
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ expect(response.parsed_body['error']).to eq(I18n.t('agent_capacity_policy.inbox_already_assigned'))
+ end
+ end
+ end
+
+ describe 'PUT /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits/{id}' do
+ let!(:inbox_limit) { create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox, conversation_limit: 5) }
+
+ context 'when admin' do
+ it 'updates the inbox limit' do
+ put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits/#{inbox_limit.id}",
+ params: { conversation_limit: 15 },
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body['conversation_limit']).to eq(15)
+ expect(inbox_limit.reload.conversation_limit).to eq(15)
+ end
+ end
+ end
+
+ describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits/{id}' do
+ let!(:inbox_limit) { create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox) }
+
+ context 'when admin' do
+ it 'removes the inbox limit' do
+ delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits/#{inbox_limit.id}",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:no_content)
+ expect(agent_capacity_policy.inbox_capacity_limits.find_by(id: inbox_limit.id)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb
new file mode 100644
index 000000000..be25151ae
--- /dev/null
+++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb
@@ -0,0 +1,66 @@
+require 'rails_helper'
+
+RSpec.describe 'Agent Capacity Policy Users API', type: :request do
+ let(:account) { create(:account) }
+ let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
+ let!(:user) { create(:user, account: account, role: :agent) }
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users' do
+ context 'when admin' do
+ it 'returns assigned users' do
+ user.account_users.first.update!(agent_capacity_policy: agent_capacity_policy)
+
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body.first['id']).to eq(user.id)
+ end
+ end
+ end
+
+ describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users' do
+ context 'when not admin' do
+ it 'requires admin role' do
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
+ params: { user_id: user.id },
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when admin' do
+ it 'assigns user to the policy' do
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
+ params: { user_id: user.id },
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(user.account_users.first.reload.agent_capacity_policy).to eq(agent_capacity_policy)
+ end
+ end
+ end
+
+ describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users/{id}' do
+ context 'when admin' do
+ before do
+ user.account_users.first.update!(agent_capacity_policy: agent_capacity_policy)
+ end
+
+ it 'removes user from the policy' do
+ delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users/#{user.id}",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:ok)
+ expect(user.account_users.first.reload.agent_capacity_policy).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb
new file mode 100644
index 000000000..d6b171fe8
--- /dev/null
+++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb
@@ -0,0 +1,202 @@
+require 'rails_helper'
+
+RSpec.describe 'Agent Capacity Policies API', type: :request do
+ let(:account) { create(:account) }
+ let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
+
+ describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated agent' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ it 'returns unauthorized for agent' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies",
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an administrator' do
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ it 'returns all agent capacity policies' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body.first['id']).to eq(agent_capacity_policy.id)
+ end
+ end
+ end
+
+ describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated agent' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+
+ it 'returns unauthorized for agent' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an administrator' do
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ it 'returns the agent capacity policy' do
+ get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body['id']).to eq(agent_capacity_policy.id)
+ end
+ end
+ end
+
+ describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ it 'returns unauthorized for agent' do
+ params = { agent_capacity_policy: { name: 'Test Policy' } }
+
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
+ params: params,
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'creates a new agent capacity policy when administrator' do
+ params = {
+ agent_capacity_policy: {
+ name: 'Test Policy',
+ description: 'Test Description',
+ exclusion_rules: { overall_capacity: 10 }
+ }
+ }
+
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
+ params: params,
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body['name']).to eq('Test Policy')
+ expect(response.parsed_body['description']).to eq('Test Description')
+ end
+
+ it 'returns validation errors for invalid data' do
+ params = { agent_capacity_policy: { name: '' } }
+
+ post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
+ params: params,
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe 'PUT /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ it 'returns unauthorized for agent' do
+ params = { agent_capacity_policy: { name: 'Updated Policy' } }
+
+ put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ params: params,
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'updates the agent capacity policy when administrator' do
+ params = { agent_capacity_policy: { name: 'Updated Policy' } }
+
+ put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ params: params,
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(response.parsed_body['name']).to eq('Updated Policy')
+ end
+ end
+ end
+
+ describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
+ context 'when it is an unauthenticated user' do
+ it 'returns unauthorized' do
+ delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+ end
+
+ context 'when it is an authenticated user' do
+ let(:agent) { create(:user, account: account, role: :agent) }
+ let(:administrator) { create(:user, account: account, role: :administrator) }
+
+ it 'returns unauthorized for agent' do
+ delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:unauthorized)
+ end
+
+ it 'deletes the agent capacity policy when administrator' do
+ delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
+ headers: administrator.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:ok)
+ expect { agent_capacity_policy.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/jobs/captain/documents/crawl_job_spec.rb b/spec/enterprise/jobs/captain/documents/crawl_job_spec.rb
index 949680d4e..a2dffcf04 100644
--- a/spec/enterprise/jobs/captain/documents/crawl_job_spec.rb
+++ b/spec/enterprise/jobs/captain/documents/crawl_job_spec.rb
@@ -105,5 +105,29 @@ RSpec.describe Captain::Documents::CrawlJob, type: :job do
described_class.perform_now(document)
end
end
+
+ context 'when document is a PDF' do
+ let(:pdf_document) do
+ doc = create(:captain_document, external_link: 'https://example.com/document')
+ allow(doc).to receive(:pdf_document?).and_return(true)
+ allow(doc).to receive(:update!).and_return(true)
+ doc
+ end
+
+ it 'processes PDF using PdfProcessingService' do
+ pdf_service = instance_double(Captain::Llm::PdfProcessingService)
+ expect(Captain::Llm::PdfProcessingService).to receive(:new).with(pdf_document).and_return(pdf_service)
+ expect(pdf_service).to receive(:process)
+ expect(pdf_document).to receive(:update!).with(status: :available)
+
+ described_class.perform_now(pdf_document)
+ end
+
+ it 'handles PDF processing errors' do
+ allow(Captain::Llm::PdfProcessingService).to receive(:new).and_raise(StandardError, 'Processing failed')
+
+ expect { described_class.perform_now(pdf_document) }.to raise_error(StandardError, 'Processing failed')
+ end
+ end
end
end
diff --git a/spec/enterprise/jobs/captain/documents/response_builder_job_spec.rb b/spec/enterprise/jobs/captain/documents/response_builder_job_spec.rb
index 4dc4bb481..73b67ee27 100644
--- a/spec/enterprise/jobs/captain/documents/response_builder_job_spec.rb
+++ b/spec/enterprise/jobs/captain/documents/response_builder_job_spec.rb
@@ -64,5 +64,41 @@ RSpec.describe Captain::Documents::ResponseBuilderJob, type: :job do
.with(spanish_document.content, 'portuguese')
end
end
+
+ context 'when processing a PDF document' do
+ let(:pdf_document) do
+ doc = create(:captain_document, assistant: assistant)
+ allow(doc).to receive(:pdf_document?).and_return(true)
+ allow(doc).to receive(:openai_file_id).and_return('file-123')
+ allow(doc).to receive(:update!).and_return(true)
+ allow(doc).to receive(:metadata).and_return({})
+ doc
+ end
+ let(:paginated_service) { instance_double(Captain::Llm::PaginatedFaqGeneratorService) }
+ let(:pdf_faqs) do
+ [{ 'question' => 'What is in the PDF?', 'answer' => 'Important content' }]
+ end
+
+ before do
+ allow(Captain::Llm::PaginatedFaqGeneratorService).to receive(:new)
+ .with(pdf_document, anything)
+ .and_return(paginated_service)
+ allow(paginated_service).to receive(:generate).and_return(pdf_faqs)
+ allow(paginated_service).to receive(:total_pages_processed).and_return(10)
+ allow(paginated_service).to receive(:iterations_completed).and_return(1)
+ end
+
+ it 'uses paginated FAQ generator for PDFs' do
+ expect(Captain::Llm::PaginatedFaqGeneratorService).to receive(:new).with(pdf_document, anything)
+
+ described_class.new.perform(pdf_document)
+ end
+
+ it 'stores pagination metadata' do
+ expect(pdf_document).to receive(:update!).with(hash_including(metadata: hash_including('faq_generation')))
+
+ described_class.new.perform(pdf_document)
+ end
+ end
end
end
diff --git a/spec/enterprise/models/agent_capacity_policy_spec.rb b/spec/enterprise/models/agent_capacity_policy_spec.rb
new file mode 100644
index 000000000..231e85423
--- /dev/null
+++ b/spec/enterprise/models/agent_capacity_policy_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe AgentCapacityPolicy, type: :model do
+ let(:account) { create(:account) }
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_length_of(:name).is_at_most(255) }
+ end
+
+ describe 'destruction' do
+ let(:policy) { create(:agent_capacity_policy, account: account) }
+ let(:user) { create(:user, account: account) }
+ let(:inbox) { create(:inbox, account: account) }
+
+ it 'destroys associated inbox capacity limits' do
+ create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
+ expect { policy.destroy }.to change(InboxCapacityLimit, :count).by(-1)
+ end
+
+ it 'nullifies associated account users' do
+ account_user = user.account_users.first
+ account_user.update!(agent_capacity_policy: policy)
+
+ policy.destroy
+ expect(account_user.reload.agent_capacity_policy).to be_nil
+ end
+ end
+end
diff --git a/spec/enterprise/models/captain/document_spec.rb b/spec/enterprise/models/captain/document_spec.rb
new file mode 100644
index 000000000..56dc1727c
--- /dev/null
+++ b/spec/enterprise/models/captain/document_spec.rb
@@ -0,0 +1,85 @@
+require 'rails_helper'
+
+RSpec.describe Captain::Document, type: :model do
+ let(:account) { create(:account) }
+ let(:assistant) { create(:captain_assistant, account: account) }
+
+ describe 'PDF support' do
+ let(:pdf_document) do
+ doc = build(:captain_document, assistant: assistant, account: account)
+ doc.pdf_file.attach(
+ io: StringIO.new('PDF content'),
+ filename: 'test.pdf',
+ content_type: 'application/pdf'
+ )
+ doc
+ end
+
+ describe 'validations' do
+ it 'allows PDF file without external link' do
+ pdf_document.external_link = nil
+ expect(pdf_document).to be_valid
+ end
+
+ it 'validates PDF file size' do
+ doc = build(:captain_document, assistant: assistant, account: account)
+ doc.pdf_file.attach(
+ io: StringIO.new('x' * 11.megabytes),
+ filename: 'large.pdf',
+ content_type: 'application/pdf'
+ )
+ doc.external_link = nil
+ expect(doc).not_to be_valid
+ expect(doc.errors[:pdf_file]).to include(I18n.t('captain.documents.pdf_size_error'))
+ end
+ end
+
+ describe '#pdf_document?' do
+ it 'returns true for attached PDF' do
+ expect(pdf_document.pdf_document?).to be true
+ end
+
+ it 'returns true for .pdf external links' do
+ doc = build(:captain_document, external_link: 'https://example.com/document.pdf')
+ expect(doc.pdf_document?).to be true
+ end
+
+ it 'returns false for non-PDF documents' do
+ doc = build(:captain_document, external_link: 'https://example.com')
+ expect(doc.pdf_document?).to be false
+ end
+ end
+
+ describe '#display_url' do
+ it 'returns Rails blob URL for attached PDFs' do
+ pdf_document.save!
+ # The display_url method calls rails_blob_url which returns a URL containing 'rails/active_storage'
+ url = pdf_document.display_url
+ expect(url).to be_present
+ end
+
+ it 'returns external_link for web documents' do
+ doc = create(:captain_document, external_link: 'https://example.com')
+ expect(doc.display_url).to eq('https://example.com')
+ end
+ end
+
+ describe '#store_openai_file_id' do
+ it 'stores the file ID in metadata' do
+ pdf_document.save!
+ pdf_document.store_openai_file_id('file-abc123')
+
+ expect(pdf_document.reload.openai_file_id).to eq('file-abc123')
+ end
+ end
+
+ describe 'automatic external_link generation' do
+ it 'generates unique external_link for PDFs' do
+ pdf_document.external_link = nil
+ pdf_document.save!
+
+ expect(pdf_document.external_link).to start_with('PDF: test_')
+ end
+ end
+ end
+end
diff --git a/spec/enterprise/models/inbox_capacity_limit_spec.rb b/spec/enterprise/models/inbox_capacity_limit_spec.rb
new file mode 100644
index 000000000..8c3f76dc4
--- /dev/null
+++ b/spec/enterprise/models/inbox_capacity_limit_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+RSpec.describe InboxCapacityLimit, type: :model do
+ let(:account) { create(:account) }
+ let(:policy) { create(:agent_capacity_policy, account: account) }
+ let(:inbox) { create(:inbox, account: account) }
+
+ describe 'validations' do
+ subject { create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox) }
+
+ it { is_expected.to validate_presence_of(:conversation_limit) }
+ it { is_expected.to validate_numericality_of(:conversation_limit).is_greater_than(0).only_integer }
+ it { is_expected.to validate_uniqueness_of(:inbox_id).scoped_to(:agent_capacity_policy_id) }
+ end
+
+ describe 'uniqueness constraint' do
+ it 'prevents duplicate inbox limits for the same policy' do
+ create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
+ duplicate = build(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
+
+ expect(duplicate).not_to be_valid
+ expect(duplicate.errors[:inbox_id]).to include('has already been taken')
+ end
+
+ it 'allows the same inbox in different policies' do
+ other_policy = create(:agent_capacity_policy, account: account)
+ create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
+
+ different_policy_limit = build(:inbox_capacity_limit, agent_capacity_policy: other_policy, inbox: inbox)
+ expect(different_policy_limit).to be_valid
+ end
+ end
+end
diff --git a/spec/enterprise/services/captain/llm/paginated_faq_generator_service_spec.rb b/spec/enterprise/services/captain/llm/paginated_faq_generator_service_spec.rb
new file mode 100644
index 000000000..5215a2d40
--- /dev/null
+++ b/spec/enterprise/services/captain/llm/paginated_faq_generator_service_spec.rb
@@ -0,0 +1,106 @@
+require 'rails_helper'
+require 'custom_exceptions/pdf_processing_error'
+
+RSpec.describe Captain::Llm::PaginatedFaqGeneratorService do
+ let(:document) { create(:captain_document) }
+ let(:service) { described_class.new(document, pages_per_chunk: 5) }
+ let(:openai_client) { instance_double(OpenAI::Client) }
+
+ before do
+ # Mock OpenAI configuration
+ installation_config = instance_double(InstallationConfig, value: 'test-api-key')
+ allow(InstallationConfig).to receive(:find_by!)
+ .with(name: 'CAPTAIN_OPEN_AI_API_KEY')
+ .and_return(installation_config)
+
+ allow(OpenAI::Client).to receive(:new).and_return(openai_client)
+ end
+
+ describe '#generate' do
+ context 'when document lacks OpenAI file ID' do
+ before do
+ allow(document).to receive(:openai_file_id).and_return(nil)
+ end
+
+ it 'raises an error' do
+ expect { service.generate }.to raise_error(CustomExceptions::PdfFaqGenerationError)
+ end
+ end
+
+ context 'when generating FAQs from PDF pages' do
+ let(:faq_response) do
+ {
+ 'choices' => [{
+ 'message' => {
+ 'content' => JSON.generate({
+ 'faqs' => [
+ { 'question' => 'What is this document about?', 'answer' => 'It explains key concepts.' }
+ ],
+ 'has_content' => true
+ })
+ }
+ }]
+ }
+ end
+
+ let(:empty_response) do
+ {
+ 'choices' => [{
+ 'message' => {
+ 'content' => JSON.generate({
+ 'faqs' => [],
+ 'has_content' => false
+ })
+ }
+ }]
+ }
+ end
+
+ before do
+ allow(document).to receive(:openai_file_id).and_return('file-123')
+ end
+
+ it 'generates FAQs from paginated content' do
+ allow(openai_client).to receive(:chat).and_return(faq_response, empty_response)
+
+ faqs = service.generate
+
+ expect(faqs).to have_attributes(size: 1)
+ expect(faqs.first['question']).to eq('What is this document about?')
+ end
+
+ it 'stops when no more content' do
+ allow(openai_client).to receive(:chat).and_return(empty_response)
+
+ faqs = service.generate
+
+ expect(faqs).to be_empty
+ end
+
+ it 'respects max iterations limit' do
+ allow(openai_client).to receive(:chat).and_return(faq_response)
+
+ # Force max iterations
+ service.instance_variable_set(:@iterations_completed, 19)
+
+ service.generate
+ expect(service.iterations_completed).to eq(20)
+ end
+ end
+ end
+
+ describe '#should_continue_processing?' do
+ it 'stops at max iterations' do
+ service.instance_variable_set(:@iterations_completed, 20)
+ expect(service.should_continue_processing?(faqs: ['faq'], has_content: true)).to be false
+ end
+
+ it 'stops when no FAQs returned' do
+ expect(service.should_continue_processing?(faqs: [], has_content: true)).to be false
+ end
+
+ it 'continues when FAQs exist and under limits' do
+ expect(service.should_continue_processing?(faqs: ['faq'], has_content: true)).to be true
+ end
+ end
+end
diff --git a/spec/enterprise/services/captain/llm/pdf_processing_service_spec.rb b/spec/enterprise/services/captain/llm/pdf_processing_service_spec.rb
new file mode 100644
index 000000000..9dc416685
--- /dev/null
+++ b/spec/enterprise/services/captain/llm/pdf_processing_service_spec.rb
@@ -0,0 +1,58 @@
+require 'rails_helper'
+require 'custom_exceptions/pdf_processing_error'
+
+RSpec.describe Captain::Llm::PdfProcessingService do
+ let(:document) { create(:captain_document) }
+ let(:service) { described_class.new(document) }
+
+ before do
+ # Mock OpenAI configuration
+ installation_config = instance_double(InstallationConfig, value: 'test-api-key')
+ allow(InstallationConfig).to receive(:find_by!)
+ .with(name: 'CAPTAIN_OPEN_AI_API_KEY')
+ .and_return(installation_config)
+ end
+
+ describe '#process' do
+ context 'when document already has OpenAI file ID' do
+ before do
+ allow(document).to receive(:openai_file_id).and_return('existing-file-id')
+ end
+
+ it 'skips upload' do
+ expect(document).not_to receive(:store_openai_file_id)
+ service.process
+ end
+ end
+
+ context 'when uploading PDF to OpenAI' do
+ let(:mock_client) { instance_double(OpenAI::Client) }
+ let(:pdf_content) { 'PDF content' }
+
+ before do
+ allow(document).to receive(:openai_file_id).and_return(nil)
+
+ # Use a simple double for ActiveStorage since it's a complex Rails object
+ pdf_file = double('pdf_file', download: pdf_content) # rubocop:disable RSpec/VerifiedDoubles
+ allow(document).to receive(:pdf_file).and_return(pdf_file)
+
+ allow(OpenAI::Client).to receive(:new).and_return(mock_client)
+ # Use a simple double for OpenAI::Files as it may not be loaded
+ files_api = double('files_api') # rubocop:disable RSpec/VerifiedDoubles
+ allow(files_api).to receive(:upload).and_return({ 'id' => 'file-abc123' })
+ allow(mock_client).to receive(:files).and_return(files_api)
+ end
+
+ it 'uploads PDF and stores file ID' do
+ expect(document).to receive(:store_openai_file_id).with('file-abc123')
+ service.process
+ end
+
+ it 'raises error when upload fails' do
+ allow(mock_client.files).to receive(:upload).and_return({ 'id' => nil })
+
+ expect { service.process }.to raise_error(CustomExceptions::PdfUploadError)
+ end
+ end
+ end
+end
diff --git a/spec/factories/agent_capacity_policies.rb b/spec/factories/agent_capacity_policies.rb
new file mode 100644
index 000000000..98a60f1fd
--- /dev/null
+++ b/spec/factories/agent_capacity_policies.rb
@@ -0,0 +1,21 @@
+FactoryBot.define do
+ factory :agent_capacity_policy do
+ account
+ sequence(:name) { |n| "Agent Capacity Policy #{n}" }
+ description { 'Test agent capacity policy' }
+ exclusion_rules { {} }
+
+ trait :with_overall_capacity do
+ exclusion_rules { { 'overall_capacity' => 10 } }
+ end
+
+ trait :with_time_exclusions do
+ exclusion_rules do
+ {
+ 'hours' => [0, 1, 2, 3, 4, 5],
+ 'days' => %w[saturday sunday]
+ }
+ end
+ end
+ end
+end
diff --git a/spec/factories/inbox_capacity_limits.rb b/spec/factories/inbox_capacity_limits.rb
new file mode 100644
index 000000000..bffd74fcd
--- /dev/null
+++ b/spec/factories/inbox_capacity_limits.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :inbox_capacity_limit do
+ association :agent_capacity_policy, factory: :agent_capacity_policy
+ inbox
+ conversation_limit { 5 }
+ end
+end
diff --git a/spec/fixtures/files/sample.pdf b/spec/fixtures/files/sample.pdf
new file mode 100644
index 000000000..9f6471c5d
--- /dev/null
+++ b/spec/fixtures/files/sample.pdf
@@ -0,0 +1,32 @@
+%PDF-1.4
+1 0 obj
+<< /Type /Catalog /Pages 2 0 R >>
+endobj
+2 0 obj
+<< /Type /Pages /Kids [3 0 R] /Count 1 >>
+endobj
+3 0 obj
+<< /Type /Page /Parent 2 0 R /Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Arial >> >> >> /MediaBox [0 0 612 792] /Contents 4 0 R >>
+endobj
+4 0 obj
+<< /Length 44 >>
+stream
+BT
+/F1 12 Tf
+100 700 Td
+(Sample PDF) Tj
+ET
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000009 00000 n
+0000000058 00000 n
+0000000115 00000 n
+0000000274 00000 n
+trailer
+<< /Size 5 /Root 1 0 R >>
+startxref
+362
+%%EOF
diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb
index cd4bc6bc1..2234c1ad6 100644
--- a/spec/models/message_spec.rb
+++ b/spec/models/message_spec.rb
@@ -613,4 +613,57 @@ RSpec.describe Message do
end
end
end
+
+ describe '#should_index?' do
+ let(:account) { create(:account) }
+ let(:conversation) { create(:conversation, account: account) }
+ let(:message) { create(:message, conversation: conversation, account: account) }
+
+ before do
+ allow(ChatwootApp).to receive(:advanced_search_allowed?).and_return(true)
+ account.enable_features('advanced_search')
+ end
+
+ context 'when advanced search is not allowed globally' do
+ before do
+ allow(ChatwootApp).to receive(:advanced_search_allowed?).and_return(false)
+ end
+
+ it 'returns false' do
+ expect(message.should_index?).to be false
+ end
+ end
+
+ context 'when advanced search feature is not enabled for account' do
+ before do
+ account.disable_features('advanced_search')
+ end
+
+ it 'returns false' do
+ expect(message.should_index?).to be false
+ end
+ end
+
+ context 'when message type is not incoming or outgoing' do
+ before do
+ message.message_type = 'activity'
+ end
+
+ it 'returns false' do
+ expect(message.should_index?).to be false
+ end
+ end
+
+ context 'when all conditions are met' do
+ it 'returns true for incoming message' do
+ message.message_type = 'incoming'
+ expect(message.should_index?).to be true
+ end
+
+ it 'returns true for outgoing message' do
+ message.message_type = 'outgoing'
+ expect(message.should_index?).to be true
+ end
+ end
+ end
end