Restructure omni services and add Chatwoot research snapshot

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

View File

@@ -0,0 +1,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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