Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
272447526c |
+6
-9
@@ -38,7 +38,7 @@ const currentSortBy = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const chatStatusOptions = computed(() => [
|
||||
const chatStatusOptions = [
|
||||
{
|
||||
label: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.open.TEXT'),
|
||||
value: 'open',
|
||||
@@ -59,9 +59,9 @@ const chatStatusOptions = computed(() => [
|
||||
label: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.all.TEXT'),
|
||||
value: 'all',
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
const chatSortOptions = computed(() => [
|
||||
const chatSortOptions = [
|
||||
{
|
||||
label: t('CHAT_LIST.SORT_ORDER_ITEMS.last_activity_at_asc.TEXT'),
|
||||
value: 'last_activity_at_asc',
|
||||
@@ -94,18 +94,15 @@ const chatSortOptions = computed(() => [
|
||||
label: t('CHAT_LIST.SORT_ORDER_ITEMS.waiting_since_desc.TEXT'),
|
||||
value: 'waiting_since_desc',
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
const activeChatStatusLabel = computed(
|
||||
() =>
|
||||
chatStatusOptions.value.find(m => m.value === chatStatusFilter.value)
|
||||
?.label || ''
|
||||
chatStatusOptions.find(m => m.value === chatStatusFilter.value)?.label || ''
|
||||
);
|
||||
|
||||
const activeChatSortLabel = computed(
|
||||
() =>
|
||||
chatSortOptions.value.find(m => m.value === chatSortFilter.value)?.label ||
|
||||
''
|
||||
() => chatSortOptions.find(m => m.value === chatSortFilter.value)?.label || ''
|
||||
);
|
||||
|
||||
const saveSelectedFilter = (type, value) => {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
shared: &shared
|
||||
version: '4.2.0'
|
||||
version: '4.1.0'
|
||||
|
||||
development:
|
||||
<<: *shared
|
||||
|
||||
@@ -84,6 +84,7 @@ dialogflow:
|
||||
{
|
||||
'project_id': { 'type': 'string' },
|
||||
'credentials': { 'type': 'object' },
|
||||
'language_code': { 'type': 'string' },
|
||||
},
|
||||
'required': ['project_id', 'credentials'],
|
||||
'additionalProperties': false,
|
||||
@@ -106,8 +107,101 @@ dialogflow:
|
||||
'validation-messages':
|
||||
{ 'JSON': 'Invalid JSON', 'required': 'Credentials is required' },
|
||||
},
|
||||
{
|
||||
'label': 'Dialogflow Language Code',
|
||||
'type': 'select',
|
||||
'name': 'language_code',
|
||||
'options': [
|
||||
{ 'label': 'en-US', 'value': 'en-US' },
|
||||
{ 'label': 'de', 'value': 'de' },
|
||||
{ 'label': 'de-at', 'value': 'de-at' },
|
||||
{ 'label': 'de-be', 'value': 'de-be' },
|
||||
{ 'label': 'de-ch', 'value': 'de-ch' },
|
||||
{ 'label': 'de-li', 'value': 'de-li' },
|
||||
{ 'label': 'en-au', 'value': 'en-au' },
|
||||
{ 'label': 'en-nz', 'value': 'en-nz' },
|
||||
{ 'label': 'en-pg', 'value': 'en-pg' },
|
||||
{ 'label': 'en-gb', 'value': 'en-gb' },
|
||||
{ 'label': 'en-bw', 'value': 'en-bw' },
|
||||
{ 'label': 'en-eg', 'value': 'en-eg' },
|
||||
{ 'label': 'en-gh', 'value': 'en-gh' },
|
||||
{ 'label': 'en-hk', 'value': 'en-hk' },
|
||||
{ 'label': 'en-ie', 'value': 'en-ie' },
|
||||
{ 'label': 'en-ke', 'value': 'en-ke' },
|
||||
{ 'label': 'en-mt', 'value': 'en-mt' },
|
||||
{ 'label': 'en-na', 'value': 'en-na' },
|
||||
{ 'label': 'en-ng', 'value': 'en-ng' },
|
||||
{ 'label': 'en-sg', 'value': 'en-sg' },
|
||||
{ 'label': 'en-tz', 'value': 'en-tz' },
|
||||
{ 'label': 'en-ug', 'value': 'en-ug' },
|
||||
{ 'label': 'en-za', 'value': 'en-za' },
|
||||
{ 'label': 'en-zm', 'value': 'en-zm' },
|
||||
{ 'label': 'en-zw', 'value': 'en-zw' },
|
||||
{ 'label': 'en-in', 'value': 'en-in' },
|
||||
{ 'label': 'en-mu', 'value': 'en-mu' },
|
||||
{ 'label': 'en', 'value': 'en' },
|
||||
{ 'label': 'en-ae', 'value': 'en-ae' },
|
||||
{ 'label': 'en-de', 'value': 'en-de' },
|
||||
{ 'label': 'en-id', 'value': 'en-id' },
|
||||
{ 'label': 'en-mm', 'value': 'en-mm' },
|
||||
{ 'label': 'en-my', 'value': 'en-my' },
|
||||
{ 'label': 'en-nl', 'value': 'en-nl' },
|
||||
{ 'label': 'en-ph', 'value': 'en-ph' },
|
||||
{ 'label': 'en-sa', 'value': 'en-sa' },
|
||||
{ 'label': 'en-se', 'value': 'en-se' },
|
||||
{ 'label': 'en-th', 'value': 'en-th' },
|
||||
{ 'label': 'es-419', 'value': 'es-419' },
|
||||
{ 'label': 'es-ar', 'value': 'es-ar' },
|
||||
{ 'label': 'es-bo', 'value': 'es-bo' },
|
||||
{ 'label': 'es-cl', 'value': 'es-cl' },
|
||||
{ 'label': 'es-co', 'value': 'es-co' },
|
||||
{ 'label': 'es-cr', 'value': 'es-cr' },
|
||||
{ 'label': 'es-do', 'value': 'es-do' },
|
||||
{ 'label': 'es-ec', 'value': 'es-ec' },
|
||||
{ 'label': 'es-gt', 'value': 'es-gt' },
|
||||
{ 'label': 'es-hn', 'value': 'es-hn' },
|
||||
{ 'label': 'es-mx', 'value': 'es-mx' },
|
||||
{ 'label': 'es-ni', 'value': 'es-ni' },
|
||||
{ 'label': 'es-pa', 'value': 'es-pa' },
|
||||
{ 'label': 'es-pe', 'value': 'es-pe' },
|
||||
{ 'label': 'es-ph', 'value': 'es-ph' },
|
||||
{ 'label': 'es-pr', 'value': 'es-pr' },
|
||||
{ 'label': 'es-py', 'value': 'es-py' },
|
||||
{ 'label': 'es-sv', 'value': 'es-sv' },
|
||||
{ 'label': 'es-us', 'value': 'es-us' },
|
||||
{ 'label': 'es-uy', 'value': 'es-uy' },
|
||||
{ 'label': 'es-ve', 'value': 'es-ve' },
|
||||
{ 'label': 'fil', 'value': 'fil' },
|
||||
{ 'label': 'tl', 'value': 'tl' },
|
||||
{ 'label': 'fil-ph', 'value': 'fil-ph' },
|
||||
{ 'label': 'tl-ph', 'value': 'tl-ph' },
|
||||
{ 'label': 'fr', 'value': 'fr' },
|
||||
{ 'label': 'fr-be', 'value': 'fr-be' },
|
||||
{ 'label': 'fr-cd', 'value': 'fr-cd' },
|
||||
{ 'label': 'fr-ch', 'value': 'fr-ch' },
|
||||
{ 'label': 'fr-ci', 'value': 'fr-ci' },
|
||||
{ 'label': 'fr-cm', 'value': 'fr-cm' },
|
||||
{ 'label': 'fr-dz', 'value': 'fr-dz' },
|
||||
{ 'label': 'fr-es', 'value': 'fr-es' },
|
||||
{ 'label': 'fr-gb', 'value': 'fr-gb' },
|
||||
{ 'label': 'fr-gp', 'value': 'fr-gp' },
|
||||
{ 'label': 'fr-ma', 'value': 'fr-ma' },
|
||||
{ 'label': 'fr-mc', 'value': 'fr-mc' },
|
||||
{ 'label': 'fr-mu', 'value': 'fr-mu' },
|
||||
{ 'label': 'fr-re', 'value': 'fr-re' },
|
||||
{ 'label': 'fr-sn', 'value': 'fr-sn' },
|
||||
{ 'label': 'fr-tn', 'value': 'fr-tn' },
|
||||
{ 'label': 'fr-us', 'value': 'fr-us' },
|
||||
{ 'label': 'it', 'value': 'it' },
|
||||
{ 'label': 'it-it', 'value': 'it-it' },
|
||||
{ 'label': 'it-ch', 'value': 'it-ch' },
|
||||
{ 'label': 'nl', 'value': 'nl' },
|
||||
{ 'label': 'nl-be', 'value': 'nl-be' },
|
||||
],
|
||||
'value': 'en-US',
|
||||
},
|
||||
]
|
||||
visible_properties: ['project_id']
|
||||
visible_properties: ['project_id', 'language_code']
|
||||
google_translate:
|
||||
id: google_translate
|
||||
logo: google-translate.png
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
module Captain::ChatHelper
|
||||
def search_documentation_tool
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'search_documentation',
|
||||
description: "Use this function to get documentation on functionalities you don't know about.",
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
search_query: {
|
||||
type: 'string',
|
||||
description: 'The search query to look up in the documentation.'
|
||||
}
|
||||
},
|
||||
required: ['search_query']
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def request_chat_completion
|
||||
Rails.logger.debug { "[CAPTAIN][ChatCompletion] #{@messages}" }
|
||||
|
||||
@@ -6,12 +26,13 @@ module Captain::ChatHelper
|
||||
parameters: {
|
||||
model: @model,
|
||||
messages: @messages,
|
||||
tools: @tool_registry&.registered_tools || [],
|
||||
tools: [search_documentation_tool],
|
||||
response_format: { type: 'json_object' }
|
||||
}
|
||||
)
|
||||
|
||||
handle_response(response)
|
||||
@response
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
@@ -20,7 +41,7 @@ module Captain::ChatHelper
|
||||
if message['tool_calls']
|
||||
process_tool_calls(message['tool_calls'])
|
||||
else
|
||||
JSON.parse(message['content'].strip)
|
||||
@response = JSON.parse(message['content'].strip)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,20 +54,38 @@ module Captain::ChatHelper
|
||||
end
|
||||
|
||||
def process_tool_call(tool_call)
|
||||
arguments = JSON.parse(tool_call['function']['arguments'])
|
||||
function_name = tool_call['function']['name']
|
||||
tool_call_id = tool_call['id']
|
||||
|
||||
if @tool_registry.respond_to?(function_name)
|
||||
execute_tool(function_name, arguments, tool_call_id)
|
||||
if tool_call['function']['name'] == 'search_documentation'
|
||||
query = JSON.parse(tool_call['function']['arguments'])['search_query']
|
||||
sections = fetch_documentation(query)
|
||||
append_tool_response(sections, tool_call_id)
|
||||
else
|
||||
process_invalid_tool_call(tool_call_id)
|
||||
append_tool_response('', tool_call_id)
|
||||
end
|
||||
end
|
||||
|
||||
def execute_tool(function_name, arguments, tool_call_id)
|
||||
result = @tool_registry.send(function_name, arguments)
|
||||
append_tool_response(result, tool_call_id)
|
||||
def fetch_documentation(query)
|
||||
Rails.logger.debug { "[CAPTAIN][DocumentationSearch] #{query}" }
|
||||
@assistant
|
||||
.responses
|
||||
.approved
|
||||
.search(query)
|
||||
.map { |response| format_response(response) }.join
|
||||
end
|
||||
|
||||
def format_response(response)
|
||||
formatted_response = "
|
||||
Question: #{response.question}
|
||||
Answer: #{response.answer}
|
||||
"
|
||||
if response.documentable.present? && response.documentable.try(:external_link)
|
||||
formatted_response += "
|
||||
Source: #{response.documentable.external_link}
|
||||
"
|
||||
end
|
||||
|
||||
formatted_response
|
||||
end
|
||||
|
||||
def append_tool_calls(tool_calls)
|
||||
@@ -56,15 +95,11 @@ module Captain::ChatHelper
|
||||
}
|
||||
end
|
||||
|
||||
def process_invalid_tool_call(tool_call_id)
|
||||
append_tool_response('Tool not available', tool_call_id)
|
||||
end
|
||||
|
||||
def append_tool_response(content, tool_call_id)
|
||||
def append_tool_response(sections, tool_call_id)
|
||||
@messages << {
|
||||
role: 'tool',
|
||||
tool_call_id: tool_call_id,
|
||||
content: content
|
||||
content: "Found the following FAQs in the documentation:\n #{sections}"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,8 +10,6 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService
|
||||
@conversation_history = config[:conversation_history]
|
||||
@previous_messages = config[:previous_messages] || []
|
||||
@language = config[:language] || 'english'
|
||||
|
||||
register_tools
|
||||
@messages = [system_message, conversation_history_context] + @previous_messages
|
||||
@response = ''
|
||||
end
|
||||
@@ -27,11 +25,6 @@ class Captain::Copilot::ChatService < Llm::BaseOpenAiService
|
||||
|
||||
private
|
||||
|
||||
def register_tools
|
||||
@tool_registry = Captain::ToolRegistryService.new(@assistant)
|
||||
@tool_registry.register_tool(Captain::Tools::SearchDocumentationService)
|
||||
end
|
||||
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -9,7 +9,6 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
@assistant = assistant
|
||||
@messages = [system_message]
|
||||
@response = ''
|
||||
register_tools
|
||||
end
|
||||
|
||||
def generate_response(input, previous_messages = [], role = 'user')
|
||||
@@ -20,11 +19,6 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService
|
||||
|
||||
private
|
||||
|
||||
def register_tools
|
||||
@tool_registry = Captain::ToolRegistryService.new(@assistant)
|
||||
@tool_registry.register_tool(Captain::Tools::SearchDocumentationService)
|
||||
end
|
||||
|
||||
def system_message
|
||||
{
|
||||
role: 'system',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
class Captain::ToolRegistryService
|
||||
attr_reader :registered_tools, :tools
|
||||
|
||||
def initialize(assistant)
|
||||
@assistant = assistant
|
||||
@registered_tools = []
|
||||
@tools = {}
|
||||
end
|
||||
|
||||
def register_tool(tool_class)
|
||||
tool = tool_class.new(@assistant)
|
||||
return unless tool.active?
|
||||
|
||||
@tools[tool.name] = tool
|
||||
@registered_tools << tool.to_registry_format
|
||||
end
|
||||
|
||||
def method_missing(method_name, *arguments)
|
||||
if @tools.key?(method_name.to_s)
|
||||
@tools[method_name.to_s].execute(*arguments)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
@tools.key?(method_name.to_s) || super
|
||||
end
|
||||
end
|
||||
@@ -1,38 +0,0 @@
|
||||
class Captain::Tools::BaseService
|
||||
attr_accessor :assistant
|
||||
|
||||
def initialize(assistant)
|
||||
@assistant = assistant
|
||||
end
|
||||
|
||||
def name
|
||||
raise NotImplementedError, "#{self.class} must implement name"
|
||||
end
|
||||
|
||||
def description
|
||||
raise NotImplementedError, "#{self.class} must implement description"
|
||||
end
|
||||
|
||||
def parameters
|
||||
raise NotImplementedError, "#{self.class} must implement parameters"
|
||||
end
|
||||
|
||||
def execute(arguments)
|
||||
raise NotImplementedError, "#{self.class} must implement execute"
|
||||
end
|
||||
|
||||
def to_registry_format
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: name,
|
||||
description: description,
|
||||
parameters: parameters
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def active?
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -1,77 +0,0 @@
|
||||
class Captain::Tools::Copilot::SearchLinearIssuesService < Captain::Tools::BaseService
|
||||
def name
|
||||
'search_linear_issues'
|
||||
end
|
||||
|
||||
def description
|
||||
'Search Linear issues based on a search term'
|
||||
end
|
||||
|
||||
def parameters
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
term: {
|
||||
type: 'string',
|
||||
description: 'The search term to find Linear issues'
|
||||
}
|
||||
},
|
||||
required: %w[term]
|
||||
}
|
||||
end
|
||||
|
||||
def execute(arguments)
|
||||
return 'Linear integration is not enabled' unless active?
|
||||
|
||||
term = arguments['term']
|
||||
|
||||
Rails.logger.info "#{self.class.name}: Service called with the search term #{term}"
|
||||
|
||||
return 'Missing required parameters' if term.blank?
|
||||
|
||||
linear_service = Integrations::Linear::ProcessorService.new(account: @assistant.account)
|
||||
result = linear_service.search_issue(term)
|
||||
|
||||
return result[:error] if result[:error]
|
||||
|
||||
issues = result[:data]
|
||||
return 'No issues found, I should try another similar search term' if issues.blank?
|
||||
|
||||
total_count = issues.length
|
||||
|
||||
<<~RESPONSE
|
||||
Total number of issues: #{total_count}
|
||||
#{issues.map { |issue| format_issue(issue) }.join("\n---\n")}
|
||||
RESPONSE
|
||||
end
|
||||
|
||||
def active?
|
||||
@assistant.account.hooks.find_by(app_id: 'linear').present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_issue(issue)
|
||||
<<~ISSUE
|
||||
Title: #{issue['title']}
|
||||
ID: #{issue['id']}
|
||||
State: #{issue['state']['name']}
|
||||
Priority: #{format_priority(issue['priority'])}
|
||||
#{issue['assignee'] ? "Assignee: #{issue['assignee']['name']}" : 'Assignee: Unassigned'}
|
||||
#{issue['description'].present? ? "\nDescription: #{issue['description']}" : ''}
|
||||
ISSUE
|
||||
end
|
||||
|
||||
def format_priority(priority)
|
||||
return 'No priority' if priority.nil?
|
||||
|
||||
case priority
|
||||
when 0 then 'No priority'
|
||||
when 1 then 'Urgent'
|
||||
when 2 then 'High'
|
||||
when 3 then 'Medium'
|
||||
when 4 then 'Low'
|
||||
else 'Unknown'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseService
|
||||
def name
|
||||
'search_documentation'
|
||||
end
|
||||
|
||||
def description
|
||||
'Search and retrieve documentation from knowledge base'
|
||||
end
|
||||
|
||||
def parameters
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
search_query: {
|
||||
type: 'string',
|
||||
description: 'The search query to look up in the documentation.'
|
||||
}
|
||||
},
|
||||
required: ['search_query']
|
||||
}
|
||||
end
|
||||
|
||||
def execute(arguments)
|
||||
query = arguments['search_query']
|
||||
Rails.logger.info { "#{self.class.name}: #{query}" }
|
||||
|
||||
responses = assistant.responses.approved.search(query)
|
||||
|
||||
return 'No FAQs found for the given query' if responses.empty?
|
||||
|
||||
responses.map { |response| format_response(response) }.join
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_response(response)
|
||||
formatted_response = "
|
||||
Question: #{response.question}
|
||||
Answer: #{response.answer}
|
||||
"
|
||||
if response.documentable.present? && response.documentable.try(:external_link)
|
||||
formatted_response += "
|
||||
Source: #{response.documentable.external_link}
|
||||
"
|
||||
end
|
||||
|
||||
formatted_response
|
||||
end
|
||||
end
|
||||
@@ -71,7 +71,8 @@ class Integrations::Dialogflow::ProcessorService < Integrations::BotProcessorSer
|
||||
def detect_intent(session_id, message)
|
||||
client = ::Google::Cloud::Dialogflow::V2::Sessions::Client.new
|
||||
session = "projects/#{hook.settings['project_id']}/agent/sessions/#{session_id}"
|
||||
query_input = { text: { text: message, language_code: 'en-US' } }
|
||||
language_code = hook.settings['language_code'] || 'en-US'
|
||||
query_input = { text: { text: message, language_code: language_code } }
|
||||
client.detect_intent session: session, query_input: query_input
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@chatwoot/chatwoot",
|
||||
"version": "4.2.0",
|
||||
"version": "4.1.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"eslint": "eslint app/**/*.{js,vue}",
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Test tool implementation
|
||||
class TestTool < Captain::Tools::BaseService
|
||||
attr_accessor :tool_active
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
@tool_active = true
|
||||
end
|
||||
|
||||
def name
|
||||
'test_tool'
|
||||
end
|
||||
|
||||
def description
|
||||
'A test tool for specs'
|
||||
end
|
||||
|
||||
def parameters
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
test_param: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def execute(*args)
|
||||
args
|
||||
end
|
||||
|
||||
def active?
|
||||
@tool_active
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Captain::ToolRegistryService do
|
||||
let(:assistant) { create(:captain_assistant) }
|
||||
let(:service) { described_class.new(assistant) }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'initializes with empty tools and registered_tools' do
|
||||
expect(service.tools).to be_empty
|
||||
expect(service.registered_tools).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#register_tool' do
|
||||
let(:tool_class) { TestTool }
|
||||
|
||||
context 'when tool is active' do
|
||||
it 'registers a new tool' do
|
||||
service.register_tool(tool_class)
|
||||
expect(service.tools['test_tool']).to be_a(TestTool)
|
||||
expect(service.registered_tools).to include(
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'test_tool',
|
||||
description: 'A test tool for specs',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
test_param: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tool is inactive' do
|
||||
it 'does not register the tool' do
|
||||
tool = tool_class.new(assistant)
|
||||
tool.tool_active = false
|
||||
allow(tool_class).to receive(:new).and_return(tool)
|
||||
|
||||
service.register_tool(tool_class)
|
||||
|
||||
expect(service.tools['test_tool']).to be_nil
|
||||
expect(service.registered_tools).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'method_missing' do
|
||||
let(:tool_class) { TestTool }
|
||||
|
||||
before do
|
||||
service.register_tool(tool_class)
|
||||
end
|
||||
|
||||
context 'when method corresponds to a registered tool' do
|
||||
it 'executes the tool with given arguments' do
|
||||
result = service.test_tool(test_param: 'arg1')
|
||||
expect(result).to eq([{ test_param: 'arg1' }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when method does not correspond to a registered tool' do
|
||||
it 'raises NoMethodError' do
|
||||
expect { service.unknown_tool }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,125 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Captain::Tools::Copilot::SearchLinearIssuesService do
|
||||
let(:account) { create(:account) }
|
||||
let(:assistant) { create(:captain_assistant, account: account) }
|
||||
let(:service) { described_class.new(assistant) }
|
||||
|
||||
describe '#name' do
|
||||
it 'returns the correct service name' do
|
||||
expect(service.name).to eq('search_linear_issues')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the service description' do
|
||||
expect(service.description).to eq('Search Linear issues based on a search term')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameters' do
|
||||
it 'returns the expected parameter schema' do
|
||||
expect(service.parameters).to eq(
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
term: {
|
||||
type: 'string',
|
||||
description: 'The search term to find Linear issues'
|
||||
}
|
||||
},
|
||||
required: %w[term]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#active?' do
|
||||
context 'when Linear integration is enabled' do
|
||||
before do
|
||||
create(:integrations_hook, :linear, account: account)
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
expect(service.active?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Linear integration is not enabled' do
|
||||
it 'returns false' do
|
||||
expect(service.active?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when Linear integration is not enabled' do
|
||||
it 'returns error message' do
|
||||
expect(service.execute({ 'term' => 'test' })).to eq('Linear integration is not enabled')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Linear integration is enabled' do
|
||||
let(:linear_service) { instance_double(Integrations::Linear::ProcessorService) }
|
||||
|
||||
before do
|
||||
create(:integrations_hook, :linear, account: account)
|
||||
allow(Integrations::Linear::ProcessorService).to receive(:new).and_return(linear_service)
|
||||
end
|
||||
|
||||
context 'when term is blank' do
|
||||
it 'returns error message' do
|
||||
expect(service.execute({ 'term' => '' })).to eq('Missing required parameters')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when search returns error' do
|
||||
before do
|
||||
allow(linear_service).to receive(:search_issue).and_return({ error: 'API Error' })
|
||||
end
|
||||
|
||||
it 'returns the error message' do
|
||||
expect(service.execute({ 'term' => 'test' })).to eq('API Error')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when search returns no issues' do
|
||||
before do
|
||||
allow(linear_service).to receive(:search_issue).and_return({ data: [] })
|
||||
end
|
||||
|
||||
it 'returns no issues found message' do
|
||||
expect(service.execute({ 'term' => 'test' })).to eq('No issues found, I should try another similar search term')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when search returns issues' do
|
||||
let(:issues) do
|
||||
[{
|
||||
'title' => 'Test Issue',
|
||||
'id' => 'TEST-123',
|
||||
'state' => { 'name' => 'In Progress' },
|
||||
'priority' => 4,
|
||||
'assignee' => { 'name' => 'John Doe' },
|
||||
'description' => 'Test description'
|
||||
}]
|
||||
end
|
||||
|
||||
before do
|
||||
allow(linear_service).to receive(:search_issue).and_return({ data: issues })
|
||||
end
|
||||
|
||||
it 'returns formatted issues' do
|
||||
result = service.execute({ 'term' => 'test' })
|
||||
expect(result).to include('Total number of issues: 1')
|
||||
expect(result).to include('Title: Test Issue')
|
||||
expect(result).to include('ID: TEST-123')
|
||||
expect(result).to include('State: In Progress')
|
||||
expect(result).to include('Priority: Low')
|
||||
expect(result).to include('Assignee: John Doe')
|
||||
expect(result).to include('Description: Test description')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,77 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Captain::Tools::SearchDocumentationService do
|
||||
let(:assistant) { create(:captain_assistant) }
|
||||
let(:service) { described_class.new(assistant) }
|
||||
let(:question) { 'How to create a new account?' }
|
||||
let(:answer) { 'You can create a new account by clicking on the Sign Up button.' }
|
||||
let(:external_link) { 'https://example.com/docs/create-account' }
|
||||
|
||||
describe '#name' do
|
||||
it 'returns the correct service name' do
|
||||
expect(service.name).to eq('search_documentation')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#description' do
|
||||
it 'returns the service description' do
|
||||
expect(service.description).to eq('Search and retrieve documentation from knowledge base')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameters' do
|
||||
it 'returns the required parameters schema' do
|
||||
expected_schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
search_query: {
|
||||
type: 'string',
|
||||
description: 'The search query to look up in the documentation.'
|
||||
}
|
||||
},
|
||||
required: ['search_query']
|
||||
}
|
||||
|
||||
expect(service.parameters).to eq(expected_schema)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let!(:response) do
|
||||
create(
|
||||
:captain_assistant_response,
|
||||
assistant: assistant,
|
||||
question: question,
|
||||
answer: answer,
|
||||
status: 'approved'
|
||||
)
|
||||
end
|
||||
|
||||
let(:documentable) { create(:captain_document, external_link: external_link) }
|
||||
|
||||
context 'when matching responses exist' do
|
||||
before do
|
||||
response.update(documentable: documentable)
|
||||
allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([response])
|
||||
end
|
||||
|
||||
it 'returns formatted responses for the search query' do
|
||||
result = service.execute({ 'search_query' => question })
|
||||
|
||||
expect(result).to include(question)
|
||||
expect(result).to include(answer)
|
||||
expect(result).to include(external_link)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no matching responses exist' do
|
||||
before do
|
||||
allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([])
|
||||
end
|
||||
|
||||
it 'returns an empty string' do
|
||||
expect(service.execute({ 'search_query' => question })).to eq('No FAQs found for the given query')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ FactoryBot.define do
|
||||
|
||||
trait :dialogflow do
|
||||
app_id { 'dialogflow' }
|
||||
settings { { project_id: 'test', credentials: {} } }
|
||||
settings { { project_id: 'test', credentials: {}, language_code: 'en-US' } }
|
||||
end
|
||||
|
||||
trait :dyte do
|
||||
|
||||
@@ -153,11 +153,12 @@ describe Integrations::Dialogflow::ProcessorService do
|
||||
let(:google_dialogflow) { Google::Cloud::Dialogflow::V2::Sessions::Client }
|
||||
let(:session_client) { double }
|
||||
let(:session) { double }
|
||||
let(:query_input) { { text: { text: message, language_code: 'en-US' } } }
|
||||
let(:language_code) { 'es-419' }
|
||||
let(:query_input) { { text: { text: message, language_code: language_code } } }
|
||||
let(:processor) { described_class.new(event_name: event_name, hook: hook, event_data: event_data) }
|
||||
|
||||
before do
|
||||
hook.update(settings: { 'project_id' => 'test', 'credentials' => 'creds' })
|
||||
hook.update(settings: { 'project_id' => 'test', 'credentials' => 'creds', 'language_code' => language_code })
|
||||
allow(google_dialogflow).to receive(:new).and_return(session_client)
|
||||
allow(session_client).to receive(:detect_intent).and_return({ session: session, query_input: query_input })
|
||||
end
|
||||
@@ -165,7 +166,7 @@ describe Integrations::Dialogflow::ProcessorService do
|
||||
it 'returns intended response' do
|
||||
response = processor.send(:get_response, conversation.contact_inbox.source_id, message.content)
|
||||
expect(response[:query_input][:text][:text]).to eq(message)
|
||||
expect(response[:query_input][:text][:language_code]).to eq('en-US')
|
||||
expect(response[:query_input][:text][:language_code]).to eq(language_code)
|
||||
end
|
||||
|
||||
it 'disables the hook if permission errors are thrown' do
|
||||
|
||||
Reference in New Issue
Block a user