Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
class Api::V1::Accounts::AgentCapacityPolicies::InboxLimitsController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
before_action -> { 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
|
||||
@@ -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 = User.joins(:account_users)
|
||||
.where(account_users: { account_id: Current.account.id, 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
|
||||
@@ -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: [:exclude_older_than_hours, { excluded_labels: [] }]
|
||||
)
|
||||
end
|
||||
|
||||
def fetch_policy
|
||||
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
class Api::V1::Accounts::AppliedSlasController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
include Sift
|
||||
include DateRangeHelper
|
||||
|
||||
RESULTS_PER_PAGE = 25
|
||||
|
||||
before_action :set_applied_slas, only: [:index, :metrics, :download]
|
||||
before_action :set_current_page, only: [:index]
|
||||
before_action :check_admin_authorization?
|
||||
|
||||
sort_on :created_at, type: :datetime
|
||||
|
||||
def index
|
||||
@count = number_of_sla_misses
|
||||
@applied_slas = @missed_applied_slas.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def metrics
|
||||
@total_applied_slas = total_applied_slas
|
||||
@number_of_sla_misses = number_of_sla_misses
|
||||
@hit_rate = hit_rate
|
||||
end
|
||||
|
||||
def download
|
||||
@missed_applied_slas = missed_applied_slas
|
||||
response.headers['Content-Type'] = 'text/csv'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=breached_conversation.csv'
|
||||
render layout: false, formats: [:csv]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def total_applied_slas
|
||||
@total_applied_slas ||= @applied_slas.count
|
||||
end
|
||||
|
||||
def number_of_sla_misses
|
||||
@number_of_sla_misses ||= missed_applied_slas.count
|
||||
end
|
||||
|
||||
def hit_rate
|
||||
number_of_sla_misses.zero? ? '100%' : "#{hit_rate_percentage}%"
|
||||
end
|
||||
|
||||
def hit_rate_percentage
|
||||
((total_applied_slas - number_of_sla_misses) / total_applied_slas.to_f * 100).round(2)
|
||||
end
|
||||
|
||||
def set_applied_slas
|
||||
initial_query = Current.account.applied_slas.includes(:conversation)
|
||||
@applied_slas = apply_filters(initial_query)
|
||||
end
|
||||
|
||||
def apply_filters(query)
|
||||
query.filter_by_date_range(range)
|
||||
.filter_by_inbox_id(params[:inbox_id])
|
||||
.filter_by_team_id(params[:team_id])
|
||||
.filter_by_sla_policy_id(params[:sla_policy_id])
|
||||
.filter_by_label_list(params[:label_list])
|
||||
.filter_by_assigned_agent_id(params[:assigned_agent_id])
|
||||
end
|
||||
|
||||
def missed_applied_slas
|
||||
@missed_applied_slas ||= @applied_slas.missed
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
class Api::V1::Accounts::AuditLogsController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
before_action :check_admin_authorization?
|
||||
before_action :fetch_audit
|
||||
|
||||
RESULTS_PER_PAGE = 15
|
||||
|
||||
def show
|
||||
@audit_logs = @audit_logs.page(params[:page]).per(RESULTS_PER_PAGE)
|
||||
@current_page = @audit_logs.current_page
|
||||
@total_entries = @audit_logs.total_count
|
||||
@per_page = RESULTS_PER_PAGE
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_audit
|
||||
@audit_logs = if audit_logs_enabled?
|
||||
Current.account.associated_audits.order(created_at: :desc)
|
||||
else
|
||||
Current.account.associated_audits.none
|
||||
end
|
||||
return if audit_logs_enabled?
|
||||
|
||||
Rails.logger.warn("Audit logs are disabled for account #{Current.account.id}")
|
||||
end
|
||||
|
||||
def audit_logs_enabled?
|
||||
Current.account.feature_enabled?(:audit_logs)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
class Api::V1::Accounts::Captain::AssistantResponsesController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Assistant) }
|
||||
|
||||
before_action :set_current_page, only: [:index]
|
||||
before_action :set_assistant, only: [:create]
|
||||
before_action :set_responses, except: [:create]
|
||||
before_action :set_response, only: [:show, :update, :destroy]
|
||||
|
||||
RESULTS_PER_PAGE = 25
|
||||
|
||||
def index
|
||||
filtered_query = apply_filters(@responses)
|
||||
@responses_count = filtered_query.count
|
||||
@responses = filtered_query.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@response = Current.account.captain_assistant_responses.new(response_params)
|
||||
@response.documentable = Current.user
|
||||
@response.save!
|
||||
end
|
||||
|
||||
def update
|
||||
@response.update!(response_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@response.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_filters(base_query)
|
||||
base_query = base_query.where(assistant_id: permitted_params[:assistant_id]) if permitted_params[:assistant_id].present?
|
||||
|
||||
if permitted_params[:document_id].present?
|
||||
base_query = base_query.where(
|
||||
documentable_id: permitted_params[:document_id],
|
||||
documentable_type: 'Captain::Document'
|
||||
)
|
||||
end
|
||||
|
||||
base_query = base_query.where(status: permitted_params[:status]) if permitted_params[:status].present?
|
||||
|
||||
if permitted_params[:search].present?
|
||||
search_term = "%#{permitted_params[:search]}%"
|
||||
base_query = base_query.where(
|
||||
'question ILIKE :search OR answer ILIKE :search',
|
||||
search: search_term
|
||||
)
|
||||
end
|
||||
|
||||
base_query
|
||||
end
|
||||
|
||||
def set_assistant
|
||||
@assistant = Current.account.captain_assistants.find_by(id: params[:assistant_id])
|
||||
end
|
||||
|
||||
def set_responses
|
||||
@responses = Current.account.captain_assistant_responses.includes(:assistant, :documentable).ordered
|
||||
end
|
||||
|
||||
def set_response
|
||||
@response = @responses.find(permitted_params[:id])
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = permitted_params[:page] || 1
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :assistant_id, :page, :document_id, :account_id, :status, :search)
|
||||
end
|
||||
|
||||
def response_params
|
||||
params.require(:assistant_response).permit(
|
||||
:question,
|
||||
:answer,
|
||||
:assistant_id,
|
||||
:status
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Assistant) }
|
||||
|
||||
before_action :set_assistant, only: [:show, :update, :destroy, :playground]
|
||||
|
||||
def index
|
||||
@assistants = account_assistants.ordered
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@assistant = account_assistants.create!(assistant_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@assistant.update!(assistant_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@assistant.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def playground
|
||||
response = Captain::Llm::AssistantChatService.new(assistant: @assistant).generate_response(
|
||||
additional_message: params[:message_content],
|
||||
message_history: message_history
|
||||
)
|
||||
|
||||
render json: response
|
||||
end
|
||||
|
||||
def tools
|
||||
assistant = Captain::Assistant.new(account: Current.account)
|
||||
@tools = assistant.available_agent_tools
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_assistant
|
||||
@assistant = account_assistants.find(params[:id])
|
||||
end
|
||||
|
||||
def account_assistants
|
||||
@account_assistants ||= Captain::Assistant.for_account(Current.account.id)
|
||||
end
|
||||
|
||||
def assistant_params
|
||||
permitted = params.require(:assistant).permit(:name, :description,
|
||||
config: [
|
||||
:product_name, :feature_faq, :feature_memory, :feature_citation,
|
||||
:welcome_message, :handoff_message, :resolution_message,
|
||||
:instructions, :temperature
|
||||
])
|
||||
|
||||
# Handle array parameters separately to allow partial updates
|
||||
permitted[:response_guidelines] = params[:assistant][:response_guidelines] if params[:assistant].key?(:response_guidelines)
|
||||
|
||||
permitted[:guardrails] = params[:assistant][:guardrails] if params[:assistant].key?(:guardrails)
|
||||
|
||||
permitted
|
||||
end
|
||||
|
||||
def playground_params
|
||||
params.require(:assistant).permit(:message_content, message_history: [:role, :content])
|
||||
end
|
||||
|
||||
def message_history
|
||||
(playground_params[:message_history] || []).map { |message| { role: message[:role], content: message[:content] } }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
class Api::V1::Accounts::Captain::BulkActionsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Assistant) }
|
||||
before_action :validate_params
|
||||
before_action :type_matches?
|
||||
|
||||
MODEL_TYPE = ['AssistantResponse'].freeze
|
||||
|
||||
def create
|
||||
@responses = process_bulk_action
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params
|
||||
return if params[:type].present? && params[:ids].present? && params[:fields].present?
|
||||
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def type_matches?
|
||||
return if MODEL_TYPE.include?(params[:type])
|
||||
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def process_bulk_action
|
||||
case params[:type]
|
||||
when 'AssistantResponse'
|
||||
handle_assistant_responses
|
||||
end
|
||||
end
|
||||
|
||||
def handle_assistant_responses
|
||||
responses = Current.account.captain_assistant_responses.where(id: params[:ids])
|
||||
return unless responses.exists?
|
||||
|
||||
case params[:fields][:status]
|
||||
when 'approve'
|
||||
responses.pending.update(status: 'approved')
|
||||
responses
|
||||
when 'delete'
|
||||
responses.destroy_all
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:type, ids: [], fields: [:status])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
class Api::V1::Accounts::Captain::CopilotMessagesController < Api::V1::Accounts::BaseController
|
||||
before_action :set_copilot_thread
|
||||
|
||||
def index
|
||||
@copilot_messages = @copilot_thread
|
||||
.copilot_messages
|
||||
.includes(:copilot_thread)
|
||||
.order(created_at: :asc)
|
||||
.page(permitted_params[:page] || 1)
|
||||
.per(1000)
|
||||
end
|
||||
|
||||
def create
|
||||
@copilot_message = @copilot_thread.copilot_messages.create!(
|
||||
message: { content: params[:message] },
|
||||
message_type: :user
|
||||
)
|
||||
@copilot_message.enqueue_response_job(params[:conversation_id], Current.user.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_copilot_thread
|
||||
@copilot_thread = Current.account.copilot_threads.find_by!(
|
||||
id: params[:copilot_thread_id],
|
||||
user: Current.user
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:page)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
class Api::V1::Accounts::Captain::CopilotThreadsController < Api::V1::Accounts::BaseController
|
||||
before_action :ensure_message, only: :create
|
||||
|
||||
def index
|
||||
@copilot_threads = Current.account.copilot_threads
|
||||
.where(user_id: Current.user.id)
|
||||
.includes(:user, :assistant)
|
||||
.order(created_at: :desc)
|
||||
.page(permitted_params[:page] || 1)
|
||||
.per(5)
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
@copilot_thread = Current.account.copilot_threads.create!(
|
||||
title: copilot_thread_params[:message],
|
||||
user: Current.user,
|
||||
assistant: assistant
|
||||
)
|
||||
|
||||
copilot_message = @copilot_thread.copilot_messages.create!(
|
||||
message_type: :user,
|
||||
message: { content: copilot_thread_params[:message] }
|
||||
)
|
||||
|
||||
build_copilot_response(copilot_message)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_copilot_response(copilot_message)
|
||||
if Current.account.usage_limits[:captain][:responses][:current_available].positive?
|
||||
copilot_message.enqueue_response_job(copilot_thread_params[:conversation_id], Current.user.id)
|
||||
else
|
||||
copilot_message.copilot_thread.copilot_messages.create!(
|
||||
message_type: :assistant,
|
||||
message: { content: I18n.t('captain.copilot_limit') }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_message
|
||||
return render_could_not_create_error(I18n.t('captain.copilot_message_required')) if copilot_thread_params[:message].blank?
|
||||
end
|
||||
|
||||
def assistant
|
||||
Current.account.captain_assistants.find(copilot_thread_params[:assistant_id])
|
||||
end
|
||||
|
||||
def copilot_thread_params
|
||||
params.permit(:message, :assistant_id, :conversation_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:page)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
class Api::V1::Accounts::Captain::CustomToolsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::CustomTool) }
|
||||
before_action :set_custom_tool, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@custom_tools = account_custom_tools.enabled
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@custom_tool = account_custom_tools.create!(custom_tool_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@custom_tool.update!(custom_tool_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@custom_tool.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_custom_tool
|
||||
@custom_tool = account_custom_tools.find(params[:id])
|
||||
end
|
||||
|
||||
def account_custom_tools
|
||||
@account_custom_tools ||= Current.account.captain_custom_tools
|
||||
end
|
||||
|
||||
def custom_tool_params
|
||||
params.require(:custom_tool).permit(
|
||||
:title,
|
||||
:description,
|
||||
:endpoint_url,
|
||||
:http_method,
|
||||
:request_template,
|
||||
:response_template,
|
||||
:auth_type,
|
||||
:enabled,
|
||||
auth_config: {},
|
||||
param_schema: [:name, :type, :description, :required]
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
class Api::V1::Accounts::Captain::DocumentsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Assistant) }
|
||||
|
||||
before_action :set_current_page, only: [:index]
|
||||
before_action :set_documents, except: [:create]
|
||||
before_action :set_document, only: [:show, :destroy]
|
||||
before_action :set_assistant, only: [:create]
|
||||
RESULTS_PER_PAGE = 25
|
||||
|
||||
def index
|
||||
base_query = @documents
|
||||
base_query = base_query.where(assistant_id: permitted_params[:assistant_id]) if permitted_params[:assistant_id].present?
|
||||
|
||||
@documents_count = base_query.count
|
||||
@documents = base_query.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
return render_could_not_create_error('Missing Assistant') if @assistant.nil?
|
||||
|
||||
@document = @assistant.documents.build(document_params)
|
||||
@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
|
||||
@document.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_documents
|
||||
@documents = Current.account.captain_documents.includes(:assistant).ordered
|
||||
end
|
||||
|
||||
def set_document
|
||||
@document = @documents.find(permitted_params[:id])
|
||||
end
|
||||
|
||||
def set_assistant
|
||||
@assistant = Current.account.captain_assistants.find_by(id: document_params[:assistant_id])
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = permitted_params[:page] || 1
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:assistant_id, :page, :id, :account_id)
|
||||
end
|
||||
|
||||
def document_params
|
||||
params.require(:document).permit(:name, :external_link, :assistant_id, :pdf_file)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
class Api::V1::Accounts::Captain::InboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Assistant) }
|
||||
|
||||
before_action :set_assistant
|
||||
def index
|
||||
@inboxes = @assistant.inboxes
|
||||
end
|
||||
|
||||
def create
|
||||
inbox = Current.account.inboxes.find(assistant_params[:inbox_id])
|
||||
@captain_inbox = @assistant.captain_inboxes.build(inbox: inbox)
|
||||
@captain_inbox.save!
|
||||
end
|
||||
|
||||
def destroy
|
||||
@captain_inbox = @assistant.captain_inboxes.find_by!(inbox_id: permitted_params[:inbox_id])
|
||||
@captain_inbox.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_assistant
|
||||
@assistant = account_assistants.find(permitted_params[:assistant_id])
|
||||
end
|
||||
|
||||
def account_assistants
|
||||
@account_assistants ||= Current.account.captain_assistants
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:assistant_id, :id, :account_id, :inbox_id)
|
||||
end
|
||||
|
||||
def assistant_params
|
||||
params.require(:inbox).permit(:inbox_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
class Api::V1::Accounts::Captain::ScenariosController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(Captain::Scenario) }
|
||||
before_action :set_assistant
|
||||
before_action :set_scenario, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@scenarios = assistant_scenarios.enabled
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@scenario = assistant_scenarios.create!(scenario_params.merge(account: Current.account))
|
||||
end
|
||||
|
||||
def update
|
||||
@scenario.update!(scenario_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@scenario.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_assistant
|
||||
@assistant = account_assistants.find(params[:assistant_id])
|
||||
end
|
||||
|
||||
def account_assistants
|
||||
@account_assistants ||= Current.account.captain_assistants
|
||||
end
|
||||
|
||||
def set_scenario
|
||||
@scenario = assistant_scenarios.find(params[:id])
|
||||
end
|
||||
|
||||
def assistant_scenarios
|
||||
@assistant.scenarios
|
||||
end
|
||||
|
||||
def scenario_params
|
||||
params.require(:scenario).permit(:title, :description, :instruction, :enabled, tools: [])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
class Api::V1::Accounts::Captain::TasksController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
|
||||
def rewrite
|
||||
result = Captain::RewriteService.new(
|
||||
account: Current.account,
|
||||
content: params[:content],
|
||||
operation: params[:operation],
|
||||
conversation_display_id: params[:conversation_display_id]
|
||||
).perform
|
||||
|
||||
render_result(result)
|
||||
end
|
||||
|
||||
def summarize
|
||||
result = Captain::SummaryService.new(
|
||||
account: Current.account,
|
||||
conversation_display_id: params[:conversation_display_id]
|
||||
).perform
|
||||
|
||||
render_result(result)
|
||||
end
|
||||
|
||||
def reply_suggestion
|
||||
result = Captain::ReplySuggestionService.new(
|
||||
account: Current.account,
|
||||
conversation_display_id: params[:conversation_display_id],
|
||||
user: Current.user
|
||||
).perform
|
||||
|
||||
render_result(result)
|
||||
end
|
||||
|
||||
def label_suggestion
|
||||
result = Captain::LabelSuggestionService.new(
|
||||
account: Current.account,
|
||||
conversation_display_id: params[:conversation_display_id]
|
||||
).perform
|
||||
|
||||
render_result(result)
|
||||
end
|
||||
|
||||
def follow_up
|
||||
result = Captain::FollowUpService.new(
|
||||
account: Current.account,
|
||||
follow_up_context: params[:follow_up_context]&.to_unsafe_h,
|
||||
user_message: params[:message],
|
||||
conversation_display_id: params[:conversation_display_id]
|
||||
).perform
|
||||
|
||||
render_result(result)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_result(result)
|
||||
if result.nil?
|
||||
render json: { message: nil }
|
||||
elsif result[:error]
|
||||
render json: { error: result[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
response_data = { message: result[:message] }
|
||||
response_data[:follow_up_context] = result[:follow_up_context] if result[:follow_up_context]
|
||||
render json: response_data
|
||||
end
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(:'captain/tasks')
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,75 @@
|
||||
class Api::V1::Accounts::CompaniesController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
include Sift
|
||||
sort_on :name, type: :string
|
||||
sort_on :domain, type: :string
|
||||
sort_on :created_at, type: :datetime
|
||||
sort_on :contacts_count, internal_name: :order_on_contacts_count, type: :scope, scope_params: [:direction]
|
||||
|
||||
RESULTS_PER_PAGE = 25
|
||||
|
||||
before_action :check_authorization
|
||||
before_action :set_current_page, only: [:index, :search]
|
||||
before_action :fetch_company, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@companies = fetch_companies(resolved_companies)
|
||||
@companies_count = @companies.total_count
|
||||
end
|
||||
|
||||
def search
|
||||
if params[:q].blank?
|
||||
return render json: { error: I18n.t('errors.companies.search.query_missing') },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
|
||||
companies = resolved_companies.search_by_name_or_domain(params[:q])
|
||||
@companies = fetch_companies(companies)
|
||||
@companies_count = @companies.total_count
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@company = Current.account.companies.build(company_params)
|
||||
@company.save!
|
||||
end
|
||||
|
||||
def update
|
||||
@company.update!(company_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@company.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolved_companies
|
||||
@resolved_companies ||= Current.account.companies
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
|
||||
def fetch_companies(companies)
|
||||
filtrate(companies)
|
||||
.page(@current_page)
|
||||
.per(RESULTS_PER_PAGE)
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless ChatwootApp.enterprise?
|
||||
|
||||
authorize(Company)
|
||||
end
|
||||
|
||||
def fetch_company
|
||||
@company = Current.account.companies.find(params[:id])
|
||||
end
|
||||
|
||||
def company_params
|
||||
params.require(:company).permit(:name, :domain, :description, :avatar)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
class Api::V1::Accounts::ConferenceController < Api::V1::Accounts::BaseController
|
||||
before_action :set_voice_inbox_for_conference
|
||||
|
||||
def token
|
||||
render json: Voice::Provider::Twilio::TokenService.new(
|
||||
inbox: @voice_inbox,
|
||||
user: Current.user,
|
||||
account: Current.account
|
||||
).generate
|
||||
end
|
||||
|
||||
def create
|
||||
conversation = fetch_conversation_by_display_id
|
||||
ensure_call_sid!(conversation)
|
||||
|
||||
conference_service = Voice::Provider::Twilio::ConferenceService.new(conversation: conversation)
|
||||
conference_sid = conference_service.ensure_conference_sid
|
||||
conference_service.mark_agent_joined(user: current_user)
|
||||
|
||||
render json: {
|
||||
status: 'success',
|
||||
id: conversation.display_id,
|
||||
conference_sid: conference_sid,
|
||||
using_webrtc: true
|
||||
}
|
||||
end
|
||||
|
||||
def destroy
|
||||
conversation = fetch_conversation_by_display_id
|
||||
Voice::Provider::Twilio::ConferenceService.new(conversation: conversation).end_conference
|
||||
render json: { status: 'success', id: conversation.display_id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_call_sid!(conversation)
|
||||
return conversation.identifier if conversation.identifier.present?
|
||||
|
||||
incoming_sid = params.require(:call_sid)
|
||||
|
||||
conversation.update!(identifier: incoming_sid)
|
||||
incoming_sid
|
||||
end
|
||||
|
||||
def set_voice_inbox_for_conference
|
||||
@voice_inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
authorize @voice_inbox, :show?
|
||||
end
|
||||
|
||||
def fetch_conversation_by_display_id
|
||||
cid = params[:conversation_id]
|
||||
raise ActiveRecord::RecordNotFound, 'conversation_id required' if cid.blank?
|
||||
|
||||
conversation = @voice_inbox.conversations.find_by!(display_id: cid)
|
||||
authorize conversation, :show?
|
||||
conversation
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,38 @@
|
||||
class Api::V1::Accounts::Contacts::CallsController < Api::V1::Accounts::BaseController
|
||||
before_action :contact
|
||||
before_action :voice_inbox
|
||||
|
||||
def create
|
||||
authorize contact, :show?
|
||||
authorize voice_inbox, :show?
|
||||
|
||||
result = Voice::OutboundCallBuilder.perform!(
|
||||
account: Current.account,
|
||||
inbox: voice_inbox,
|
||||
user: Current.user,
|
||||
contact: contact
|
||||
)
|
||||
|
||||
conversation = result[:conversation]
|
||||
|
||||
render json: {
|
||||
conversation_id: conversation.display_id,
|
||||
inbox_id: voice_inbox.id,
|
||||
call_sid: result[:call_sid],
|
||||
conference_sid: conversation.additional_attributes['conference_sid']
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contact
|
||||
@contact ||= Current.account.contacts.find(params[:id])
|
||||
end
|
||||
|
||||
def voice_inbox
|
||||
@voice_inbox ||= Current.user.assigned_inboxes.where(
|
||||
account_id: Current.account.id,
|
||||
channel_type: 'Channel::Voice'
|
||||
).find(params.require(:inbox_id))
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
class Api::V1::Accounts::CustomRolesController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
before_action :fetch_custom_role, only: [:show, :update, :destroy]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@custom_roles = Current.account.custom_roles
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@custom_role = Current.account.custom_roles.create!(permitted_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@custom_role.update!(permitted_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@custom_role.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:custom_role).permit(:name, :description, permissions: [])
|
||||
end
|
||||
|
||||
def fetch_custom_role
|
||||
@custom_role = Current.account.custom_roles.find_by(id: params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
class Api::V1::Accounts::EnterpriseAccountsController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
class Api::V1::Accounts::ReportingEventsController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
include DateRangeHelper
|
||||
|
||||
RESULTS_PER_PAGE = 25
|
||||
|
||||
before_action :check_admin_authorization?
|
||||
before_action :set_reporting_events, only: [:index]
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
def index
|
||||
@reporting_events = @reporting_events.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
@total_count = @reporting_events.total_count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_reporting_events
|
||||
@reporting_events = Current.account.reporting_events
|
||||
.includes(:conversation, :user, :inbox)
|
||||
.filter_by_date_range(range)
|
||||
.filter_by_inbox_id(params[:inbox_id])
|
||||
.filter_by_user_id(params[:user_id])
|
||||
.filter_by_name(params[:name])
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
|
||||
def set_current_page
|
||||
@current_page = (params[:page] || 1).to_i
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
class Api::V1::Accounts::SamlSettingsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_saml_sso_enabled
|
||||
before_action :check_saml_feature_enabled
|
||||
before_action :check_authorization
|
||||
before_action :set_saml_settings
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@saml_settings = Current.account.build_saml_settings(saml_settings_params)
|
||||
if @saml_settings.save
|
||||
render :show
|
||||
else
|
||||
render json: { errors: @saml_settings.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @saml_settings.update(saml_settings_params)
|
||||
render :show
|
||||
else
|
||||
render json: { errors: @saml_settings.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@saml_settings.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_saml_settings
|
||||
@saml_settings = Current.account.saml_settings ||
|
||||
Current.account.build_saml_settings
|
||||
end
|
||||
|
||||
def saml_settings_params
|
||||
params.require(:saml_settings).permit(
|
||||
:sso_url,
|
||||
:certificate,
|
||||
:idp_entity_id,
|
||||
:sp_entity_id,
|
||||
role_mappings: {}
|
||||
)
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
authorize(AccountSamlSettings)
|
||||
end
|
||||
|
||||
def check_saml_feature_enabled
|
||||
return if Current.account.feature_enabled?('saml')
|
||||
|
||||
render json: { error: I18n.t('errors.saml.feature_not_enabled') }, status: :forbidden
|
||||
end
|
||||
|
||||
def check_saml_sso_enabled
|
||||
return if GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s == 'true'
|
||||
|
||||
render json: { error: I18n.t('errors.saml.sso_not_enabled') }, status: :forbidden
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
class Api::V1::Accounts::SlaPoliciesController < Api::V1::Accounts::EnterpriseAccountsController
|
||||
before_action :fetch_sla, only: [:show, :update, :destroy]
|
||||
before_action :check_authorization
|
||||
|
||||
def index
|
||||
@sla_policies = Current.account.sla_policies
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@sla_policy = Current.account.sla_policies.create!(permitted_params)
|
||||
end
|
||||
|
||||
def update
|
||||
@sla_policy.update!(permitted_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
::DeleteObjectJob.perform_later(@sla_policy, Current.user, request.ip) if @sla_policy.present?
|
||||
head :ok
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.require(:sla_policy).permit(:name, :description, :first_response_time_threshold, :next_response_time_threshold,
|
||||
:resolution_time_threshold, :only_during_business_hours)
|
||||
end
|
||||
|
||||
def fetch_sla
|
||||
@sla_policy = Current.account.sla_policies.find_by(id: params[:id])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
class Api::V1::AuthController < Api::BaseController
|
||||
skip_before_action :authenticate_user!, only: [:saml_login]
|
||||
before_action :find_user_and_account, only: [:saml_login]
|
||||
|
||||
def saml_login
|
||||
unless saml_sso_enabled?
|
||||
render json: { error: 'SAML SSO login is not enabled' }, status: :forbidden
|
||||
return
|
||||
end
|
||||
|
||||
return if @account.nil?
|
||||
|
||||
relay_state = params[:target] || 'web'
|
||||
|
||||
saml_initiation_url = "/auth/saml?account_id=#{@account.id}&RelayState=#{relay_state}"
|
||||
redirect_to saml_initiation_url, status: :temporary_redirect
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_user_and_account
|
||||
return unless validate_email_presence
|
||||
|
||||
find_saml_enabled_account
|
||||
end
|
||||
|
||||
def validate_email_presence
|
||||
@email = params[:email]&.downcase&.strip
|
||||
return true if @email.present?
|
||||
|
||||
render json: { error: I18n.t('auth.saml.invalid_email') }, status: :bad_request
|
||||
false
|
||||
end
|
||||
|
||||
def find_saml_enabled_account
|
||||
user = User.from_email(@email)
|
||||
return render_saml_error unless user
|
||||
|
||||
account_user = find_account_with_saml(user)
|
||||
return render_saml_error unless account_user
|
||||
|
||||
@account = account_user.account
|
||||
end
|
||||
|
||||
def find_account_with_saml(user)
|
||||
user.account_users
|
||||
.joins(account: :saml_settings)
|
||||
.where.not(saml_settings: { sso_url: [nil, ''] })
|
||||
.where.not(saml_settings: { certificate: [nil, ''] })
|
||||
.find { |account_user| account_user.account.feature_enabled?('saml') }
|
||||
end
|
||||
|
||||
def render_saml_error
|
||||
error = 'saml-authentication-failed'
|
||||
|
||||
if mobile_target?
|
||||
mobile_deep_link_base = GlobalConfigService.load('MOBILE_DEEP_LINK_BASE', 'chatwootapp')
|
||||
redirect_to "#{mobile_deep_link_base}://auth/saml?error=#{ERB::Util.url_encode(error)}", allow_other_host: true
|
||||
else
|
||||
redirect_to sso_login_page_url(error: error)
|
||||
end
|
||||
end
|
||||
|
||||
def mobile_target?
|
||||
params[:target]&.casecmp('mobile')&.zero?
|
||||
end
|
||||
|
||||
def sso_login_page_url(error: nil)
|
||||
frontend_url = ENV.fetch('FRONTEND_URL', nil)
|
||||
params = { error: error }.compact
|
||||
|
||||
query = params.to_query
|
||||
query_fragment = query.present? ? "?#{query}" : ''
|
||||
|
||||
"#{frontend_url}/app/login/sso#{query_fragment}"
|
||||
end
|
||||
|
||||
def saml_sso_enabled?
|
||||
GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s == 'true'
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user