Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da0a2661ac |
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assigns agent/team to a conversation
|
||||
def create
|
||||
if params.key?(:assignee_id) || agent_bot_assignment?
|
||||
if params.key?(:assignee_id) || ai_assignment?
|
||||
set_agent
|
||||
elsif params.key?(:team_id)
|
||||
set_team
|
||||
@@ -28,6 +28,8 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
|
||||
render partial: 'api/v1/models/agent', formats: [:json], locals: { resource: resource }
|
||||
when AgentBot
|
||||
render partial: 'api/v1/models/agent_bot_slim', formats: [:json], locals: { resource: resource }
|
||||
when Captain::Assistant
|
||||
render partial: 'api/v1/models/captain_assistant_slim', formats: [:json], locals: { resource: resource }
|
||||
else
|
||||
render json: nil
|
||||
end
|
||||
@@ -39,7 +41,7 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
|
||||
render json: @team
|
||||
end
|
||||
|
||||
def agent_bot_assignment?
|
||||
params[:assignee_type].to_s == 'AgentBot'
|
||||
def ai_assignment?
|
||||
params[:assignee_type].to_s.in?(%w[AgentBot CaptainAssistant])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
# assignee_agent_bot_id :bigint
|
||||
# assignee_captain_assistant_id :bigint
|
||||
# assignee_id :integer
|
||||
# campaign_id :bigint
|
||||
# contact_id :bigint
|
||||
@@ -66,7 +67,7 @@ class Conversation < ApplicationRecord
|
||||
validates :inbox_id, presence: true
|
||||
validates :contact_id, presence: true
|
||||
before_validation :validate_additional_attributes
|
||||
before_validation :reset_agent_bot_when_assignee_present
|
||||
before_validation :reset_ai_assignees_when_assignee_present
|
||||
validates :additional_attributes, jsonb_attributes_length: true
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
validates :uuid, uniqueness: true
|
||||
@@ -104,6 +105,7 @@ class Conversation < ApplicationRecord
|
||||
belongs_to :inbox
|
||||
belongs_to :assignee, class_name: 'User', optional: true, inverse_of: :assigned_conversations
|
||||
belongs_to :assignee_agent_bot, class_name: 'AgentBot', optional: true
|
||||
belongs_to :assignee_captain_assistant, class_name: 'Captain::Assistant', optional: true
|
||||
belongs_to :contact
|
||||
belongs_to :contact_inbox
|
||||
belongs_to :team, optional: true
|
||||
@@ -195,6 +197,7 @@ class Conversation < ApplicationRecord
|
||||
|
||||
# Virtual attribute till we switch completely to polymorphic assignee
|
||||
def assignee_type
|
||||
return 'CaptainAssistant' if assignee_captain_assistant_id.present?
|
||||
return 'AgentBot' if assignee_agent_bot_id.present?
|
||||
return 'User' if assignee_id.present?
|
||||
|
||||
@@ -202,7 +205,7 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def assigned_entity
|
||||
assignee_agent_bot || assignee
|
||||
assignee_captain_assistant || assignee_agent_bot || assignee
|
||||
end
|
||||
|
||||
def tweet?
|
||||
@@ -271,10 +274,11 @@ class Conversation < ApplicationRecord
|
||||
self.additional_attributes = {} unless additional_attributes.is_a?(Hash)
|
||||
end
|
||||
|
||||
def reset_agent_bot_when_assignee_present
|
||||
def reset_ai_assignees_when_assignee_present
|
||||
return if assignee_id.blank?
|
||||
|
||||
self.assignee_agent_bot_id = nil
|
||||
self.assignee_captain_assistant_id = nil
|
||||
end
|
||||
|
||||
def determine_conversation_status
|
||||
@@ -308,8 +312,8 @@ class Conversation < ApplicationRecord
|
||||
end
|
||||
|
||||
def list_of_keys
|
||||
%w[team_id assignee_id assignee_agent_bot_id status snoozed_until custom_attributes label_list waiting_since
|
||||
first_reply_created_at priority]
|
||||
%w[team_id assignee_id assignee_agent_bot_id assignee_captain_assistant_id status snoozed_until custom_attributes label_list
|
||||
waiting_since first_reply_created_at priority]
|
||||
end
|
||||
|
||||
def allowed_keys?
|
||||
|
||||
@@ -6,7 +6,10 @@ class Conversations::AssignmentService
|
||||
end
|
||||
|
||||
def perform
|
||||
agent_bot_assignment? ? assign_agent_bot : assign_agent
|
||||
return assign_agent_bot if agent_bot_assignment?
|
||||
return assign_captain_assistant if captain_assistant_assignment?
|
||||
|
||||
assign_agent
|
||||
end
|
||||
|
||||
private
|
||||
@@ -14,8 +17,11 @@ class Conversations::AssignmentService
|
||||
attr_reader :conversation, :assignee_id, :assignee_type
|
||||
|
||||
def assign_agent
|
||||
captain_owned = conversation.assignee_captain_assistant_id.present?
|
||||
conversation.assignee = assignee
|
||||
conversation.assignee_agent_bot = nil
|
||||
conversation.assignee_captain_assistant = nil
|
||||
conversation.status = :open if assignee.present? && captain_owned && conversation.pending?
|
||||
conversation.save!
|
||||
assignee
|
||||
end
|
||||
@@ -25,10 +31,22 @@ class Conversations::AssignmentService
|
||||
|
||||
conversation.assignee = nil
|
||||
conversation.assignee_agent_bot = agent_bot
|
||||
conversation.assignee_captain_assistant = nil
|
||||
conversation.save!
|
||||
agent_bot
|
||||
end
|
||||
|
||||
def assign_captain_assistant
|
||||
return unless captain_assistant
|
||||
|
||||
conversation.assignee = nil
|
||||
conversation.assignee_agent_bot = nil
|
||||
conversation.assignee_captain_assistant = captain_assistant
|
||||
conversation.status = :pending
|
||||
conversation.save!
|
||||
captain_assistant
|
||||
end
|
||||
|
||||
def assignee
|
||||
@assignee ||= conversation.account.users.find_by(id: assignee_id)
|
||||
end
|
||||
@@ -37,7 +55,15 @@ class Conversations::AssignmentService
|
||||
@agent_bot ||= AgentBot.accessible_to(conversation.account).find_by(id: assignee_id)
|
||||
end
|
||||
|
||||
def captain_assistant
|
||||
@captain_assistant ||= Captain::Assistant.for_account(conversation.account_id).find_by(id: assignee_id)
|
||||
end
|
||||
|
||||
def agent_bot_assignment?
|
||||
assignee_type.to_s == 'AgentBot'
|
||||
end
|
||||
|
||||
def captain_assistant_assignment?
|
||||
assignee_type.to_s == 'CaptainAssistant'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,12 @@ json.meta do
|
||||
json.partial! 'api/v1/models/contact', formats: [:json], resource: conversation.contact
|
||||
end
|
||||
json.channel conversation.inbox.try(:channel_type)
|
||||
if conversation.assigned_entity.is_a?(AgentBot)
|
||||
if conversation.assignee_type == 'CaptainAssistant'
|
||||
json.assignee do
|
||||
json.partial! 'api/v1/models/captain_assistant_slim', formats: [:json], resource: conversation.assigned_entity
|
||||
end
|
||||
json.assignee_type 'CaptainAssistant'
|
||||
elsif conversation.assigned_entity.is_a?(AgentBot)
|
||||
json.assignee do
|
||||
json.partial! 'api/v1/models/agent_bot_slim', formats: [:json], resource: conversation.assigned_entity
|
||||
end
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
json.id resource.id
|
||||
json.name resource.name
|
||||
json.description resource.description
|
||||
json.thumbnail resource.push_event_data[:avatar_url]
|
||||
@@ -195,24 +195,6 @@
|
||||
display_title: 'OpenAI API Endpoint (optional)'
|
||||
description: 'The OpenAI endpoint configured for use in Captain AI. Default: https://api.openai.com/'
|
||||
locked: false
|
||||
- name: CAPTAIN_ANTHROPIC_API_KEY
|
||||
display_title: 'Anthropic API Key'
|
||||
description: 'The API key used to authenticate requests to Anthropic models for Captain AI.'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CAPTAIN_ANTHROPIC_API_BASE
|
||||
display_title: 'Anthropic API Base (optional)'
|
||||
description: 'The Anthropic endpoint configured for use in Captain AI. Defaults to RubyLLM provider settings.'
|
||||
locked: false
|
||||
- name: CAPTAIN_GEMINI_API_KEY
|
||||
display_title: 'Gemini API Key'
|
||||
description: 'The API key used to authenticate requests to Gemini models for Captain AI.'
|
||||
locked: false
|
||||
type: secret
|
||||
- name: CAPTAIN_GEMINI_API_BASE
|
||||
display_title: 'Gemini API Base (optional)'
|
||||
description: 'The Gemini endpoint configured for use in Captain AI. Defaults to RubyLLM provider settings.'
|
||||
locked: false
|
||||
- name: CAPTAIN_EMBEDDING_MODEL
|
||||
display_title: 'Embedding Model (optional)'
|
||||
description: 'The embedding model configured for use in Captain AI. Default: text-embedding-3-small'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddAssigneeCaptainAssistantToConversations < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_reference :conversations, :assignee_captain_assistant, type: :bigint, index: true
|
||||
end
|
||||
end
|
||||
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_20_000000) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_28_000000) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -726,11 +726,13 @@ ActiveRecord::Schema[7.1].define(version: 2026_06_20_000000) do
|
||||
t.datetime "waiting_since"
|
||||
t.text "cached_label_list"
|
||||
t.bigint "assignee_agent_bot_id"
|
||||
t.bigint "assignee_captain_assistant_id"
|
||||
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
||||
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
|
||||
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
|
||||
t.index ["account_id"], name: "index_conversations_on_account_id"
|
||||
t.index ["assignee_id", "account_id"], name: "index_conversations_on_assignee_id_and_account_id"
|
||||
t.index ["assignee_captain_assistant_id"], name: "index_conversations_on_assignee_captain_assistant_id"
|
||||
t.index ["campaign_id"], name: "index_conversations_on_campaign_id"
|
||||
t.index ["contact_id"], name: "index_conversations_on_contact_id"
|
||||
t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id"
|
||||
|
||||
+15
-93
@@ -2,25 +2,11 @@ require 'ruby_llm'
|
||||
|
||||
module Llm::Config
|
||||
DEFAULT_MODEL = 'gpt-4.1-mini'.freeze
|
||||
DEFAULT_PROVIDER = 'openai'.freeze
|
||||
|
||||
PROVIDER_CONFIGS = {
|
||||
'openai' => {
|
||||
api_key: 'CAPTAIN_OPEN_AI_API_KEY',
|
||||
api_base: 'CAPTAIN_OPEN_AI_ENDPOINT'
|
||||
},
|
||||
'anthropic' => {
|
||||
api_key: 'CAPTAIN_ANTHROPIC_API_KEY',
|
||||
api_base: 'CAPTAIN_ANTHROPIC_API_BASE'
|
||||
},
|
||||
'gemini' => {
|
||||
api_key: 'CAPTAIN_GEMINI_API_KEY',
|
||||
api_base: 'CAPTAIN_GEMINI_API_BASE'
|
||||
}
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
def initialized? = @initialized ||= false
|
||||
def initialized?
|
||||
@initialized ||= false
|
||||
end
|
||||
|
||||
def initialize!
|
||||
return if @initialized
|
||||
@@ -29,101 +15,37 @@ module Llm::Config
|
||||
@initialized = true
|
||||
end
|
||||
|
||||
def reset! = @initialized = false
|
||||
def reset!
|
||||
@initialized = false
|
||||
end
|
||||
|
||||
def with_api_key(api_key, provider: DEFAULT_PROVIDER, api_base: nil)
|
||||
def with_api_key(api_key, api_base: nil)
|
||||
initialize!
|
||||
context = RubyLLM.context do |config|
|
||||
configure_provider(config, provider: provider, api_key: api_key, api_base: api_base)
|
||||
config.openai_api_key = api_key
|
||||
config.openai_api_base = api_base
|
||||
end
|
||||
|
||||
yield context
|
||||
end
|
||||
|
||||
def ruby_llm_provider_supported?(provider)
|
||||
RubyLLM::Provider.providers.key?(provider.to_s.to_sym)
|
||||
end
|
||||
|
||||
def provider_options
|
||||
PROVIDER_CONFIGS.keys.each_with_object({}) do |provider, result|
|
||||
next unless ruby_llm_provider_supported?(provider)
|
||||
|
||||
result[provider] = ruby_llm_provider_name(provider)
|
||||
end
|
||||
end
|
||||
|
||||
def api_key_for(provider)
|
||||
installation_config_value(provider, :api_key)
|
||||
end
|
||||
|
||||
def api_base_for(provider)
|
||||
api_base = installation_config_value(provider, :api_base).presence
|
||||
return if api_base.blank?
|
||||
|
||||
normalized_api_base(provider, api_base)
|
||||
end
|
||||
|
||||
def provider_configured?(provider)
|
||||
api_key_for(provider).present?
|
||||
end
|
||||
|
||||
def openai_provider?(provider)
|
||||
provider.to_s == DEFAULT_PROVIDER
|
||||
end
|
||||
|
||||
def supports_tools_and_schema?(provider)
|
||||
openai_provider?(provider)
|
||||
end
|
||||
|
||||
def configure_provider(config, provider:, api_key:, api_base: nil)
|
||||
provider = provider.to_s
|
||||
options = provider_configuration_options(provider)
|
||||
api_key_option = :"#{provider}_api_key"
|
||||
api_base_option = :"#{provider}_api_base"
|
||||
|
||||
set_config_value(config, api_key_option, api_key) if api_key.present? && options.include?(api_key_option)
|
||||
set_config_value(config, api_base_option, api_base) if api_base.present? && options.include?(api_base_option)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configure_ruby_llm
|
||||
RubyLLM.configure do |config|
|
||||
PROVIDER_CONFIGS.each_key do |provider|
|
||||
next unless ruby_llm_provider_supported?(provider)
|
||||
|
||||
configure_provider(config, provider: provider, api_key: api_key_for(provider), api_base: api_base_for(provider))
|
||||
end
|
||||
config.openai_api_key = system_api_key if system_api_key.present?
|
||||
config.openai_api_base = openai_endpoint.chomp('/') if openai_endpoint.present?
|
||||
config.model_registry_file = Rails.root.join('config/llm_models.json').to_s
|
||||
config.logger = Rails.logger
|
||||
end
|
||||
end
|
||||
|
||||
def ruby_llm_provider_name(provider)
|
||||
RubyLLM::Provider.providers[provider.to_s.to_sym].name
|
||||
def system_api_key
|
||||
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_API_KEY')&.value
|
||||
end
|
||||
|
||||
def provider_configuration_options(provider)
|
||||
RubyLLM::Provider.providers[provider.to_s.to_sym]&.configuration_options || []
|
||||
end
|
||||
|
||||
def set_config_value(config, option, value)
|
||||
setter = :"#{option}="
|
||||
config.public_send(setter, value) if config.respond_to?(setter)
|
||||
end
|
||||
|
||||
def installation_config_value(provider, key)
|
||||
config_name = PROVIDER_CONFIGS.dig(provider.to_s, key)
|
||||
return if config_name.blank?
|
||||
|
||||
InstallationConfig.find_by(name: config_name)&.value
|
||||
end
|
||||
|
||||
def normalized_api_base(provider, api_base)
|
||||
endpoint = api_base.chomp('/').delete_suffix('/chat/completions')
|
||||
return "#{endpoint}/v1" if openai_provider?(provider) && endpoint.exclude?('/v1')
|
||||
|
||||
endpoint
|
||||
def openai_endpoint
|
||||
InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+2
-16
@@ -12,14 +12,11 @@ module Llm::Models
|
||||
end
|
||||
|
||||
def default_model_for(feature)
|
||||
default_model = features.dig(feature.to_s, 'default')
|
||||
return default_model if supported_model?(default_model)
|
||||
|
||||
models_for(feature).first
|
||||
features.dig(feature.to_s, 'default')
|
||||
end
|
||||
|
||||
def models_for(feature)
|
||||
(features.dig(feature.to_s, 'models') || []).select { |model_name| supported_model?(model_name) }
|
||||
features.dig(feature.to_s, 'models') || []
|
||||
end
|
||||
|
||||
def valid_model_for?(feature, model_name)
|
||||
@@ -34,17 +31,6 @@ module Llm::Models
|
||||
model_config(model_name)&.dig('provider')
|
||||
end
|
||||
|
||||
def supported_provider?(provider)
|
||||
providers.key?(provider.to_s) && Llm::Config.ruby_llm_provider_supported?(provider)
|
||||
end
|
||||
|
||||
def supported_model?(model_name)
|
||||
config = model_config(model_name)
|
||||
return false unless config
|
||||
|
||||
supported_provider?(config['provider'])
|
||||
end
|
||||
|
||||
def feature_config(feature_key)
|
||||
feature = features[feature_key.to_s]
|
||||
return nil unless feature
|
||||
|
||||
@@ -37,6 +37,7 @@ RSpec.describe 'Conversation Assignment API', type: :request do
|
||||
context 'when it is an authenticated user with access to the inbox' do
|
||||
let(:agent) { create(:user, account: account, role: :agent) }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
let(:captain_assistant) { create(:captain_assistant, account: account) }
|
||||
let(:team) { create(:team, account: account) }
|
||||
|
||||
before do
|
||||
@@ -74,6 +75,27 @@ RSpec.describe 'Conversation Assignment API', type: :request do
|
||||
expect(conversation.assignee).to be_nil
|
||||
end
|
||||
|
||||
it 'assigns a Captain assistant to the conversation' do
|
||||
params = { assignee_id: captain_assistant.id, assignee_type: 'CaptainAssistant' }
|
||||
|
||||
expect(Conversations::AssignmentService).to receive(:new)
|
||||
.with(hash_including(conversation: conversation, assignee_id: captain_assistant.id, assignee_type: 'CaptainAssistant'))
|
||||
.and_call_original
|
||||
|
||||
post api_v1_account_conversation_assignments_url(account_id: account.id, conversation_id: conversation.display_id),
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
expect(response.parsed_body['name']).to eq(captain_assistant.name)
|
||||
conversation.reload
|
||||
expect(conversation.assignee_captain_assistant).to eq(captain_assistant)
|
||||
expect(conversation.assignee_agent_bot).to be_nil
|
||||
expect(conversation.assignee).to be_nil
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
|
||||
it 'assigns a team to the conversation' do
|
||||
team_member = create(:user, account: account, role: :agent, auto_offline: false)
|
||||
create(:inbox_member, inbox: conversation.inbox, user: team_member)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Llm::Config do
|
||||
describe '.provider_options' do
|
||||
it 'returns configured providers supported by RubyLLM' do
|
||||
expect(described_class.provider_options).to include(
|
||||
'openai' => 'OpenAI',
|
||||
'anthropic' => 'Anthropic',
|
||||
'gemini' => 'Gemini'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.api_base_for' do
|
||||
it 'normalizes OpenAI-compatible endpoints to the v1 base' do
|
||||
create(:installation_config, name: 'CAPTAIN_OPEN_AI_ENDPOINT', value: 'https://proxy.example.com/chat/completions')
|
||||
|
||||
expect(described_class.api_base_for('openai')).to eq('https://proxy.example.com/v1')
|
||||
end
|
||||
|
||||
it 'keeps non-OpenAI provider endpoints unchanged except trailing slashes' do
|
||||
create(:installation_config, name: 'CAPTAIN_ANTHROPIC_API_BASE', value: 'https://anthropic.example.com/')
|
||||
|
||||
expect(described_class.api_base_for('anthropic')).to eq('https://anthropic.example.com')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.supports_tools_and_schema?' do
|
||||
it 'allows tool and schema configuration only for OpenAI' do
|
||||
expect(described_class.supports_tools_and_schema?('openai')).to be true
|
||||
expect(described_class.supports_tools_and_schema?('anthropic')).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -40,16 +40,6 @@ RSpec.describe Llm::Models do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.models_for' do
|
||||
it 'filters out models whose provider is not supported by RubyLLM' do
|
||||
allow(Llm::Config).to receive(:ruby_llm_provider_supported?) do |provider|
|
||||
provider.to_s != 'anthropic'
|
||||
end
|
||||
|
||||
expect(described_class.models_for('assistant')).not_to include('claude-haiku-4.5')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.feature_config' do
|
||||
it 'returns model metadata for a feature' do
|
||||
config = described_class.feature_config('editor')
|
||||
|
||||
@@ -4,35 +4,39 @@ describe Conversations::AssignmentService do
|
||||
let(:account) { create(:account) }
|
||||
let(:agent) { create(:user, account: account) }
|
||||
let(:agent_bot) { create(:agent_bot, account: account) }
|
||||
let(:captain_assistant) { create(:captain_assistant, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when assignee_id is blank' do
|
||||
before do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: agent_bot)
|
||||
conversation.update!(assignee_agent_bot: agent_bot, assignee_captain_assistant: captain_assistant)
|
||||
end
|
||||
|
||||
it 'clears both human and bot assignees' do
|
||||
it 'clears human and AI assignees' do
|
||||
described_class.new(conversation: conversation, assignee_id: nil).perform
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a user' do
|
||||
before do
|
||||
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil)
|
||||
conversation.update!(assignee_captain_assistant: captain_assistant, assignee: nil, status: :pending)
|
||||
end
|
||||
|
||||
it 'sets the agent and clears agent bot' do
|
||||
it 'sets the agent and clears AI ownership' do
|
||||
result = described_class.new(conversation: conversation, assignee_id: agent.id).perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(agent)
|
||||
expect(conversation.assignee_id).to eq(agent.id)
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
expect(conversation.status).to eq('open')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,8 +49,8 @@ describe Conversations::AssignmentService do
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets the agent bot and clears human assignee' do
|
||||
conversation.update!(assignee: agent, assignee_agent_bot: nil)
|
||||
it 'sets the agent bot and clears other assignees' do
|
||||
conversation.update!(assignee_agent_bot: nil, assignee_captain_assistant: captain_assistant)
|
||||
|
||||
result = service.perform
|
||||
|
||||
@@ -54,6 +58,30 @@ describe Conversations::AssignmentService do
|
||||
expect(result).to eq(agent_bot)
|
||||
expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id)
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.assignee_captain_assistant_id).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assigning a Captain assistant' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
assignee_id: captain_assistant.id,
|
||||
assignee_type: 'CaptainAssistant'
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets the Captain assistant, clears other assignees, and marks pending' do
|
||||
conversation.update!(assignee_agent_bot: agent_bot, status: :resolved)
|
||||
|
||||
result = service.perform
|
||||
|
||||
conversation.reload
|
||||
expect(result).to eq(captain_assistant)
|
||||
expect(conversation.assignee_captain_assistant_id).to eq(captain_assistant.id)
|
||||
expect(conversation.assignee_agent_bot_id).to be_nil
|
||||
expect(conversation.assignee_id).to be_nil
|
||||
expect(conversation.status).to eq('pending')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user