feat: Implementa e aprimora funcionalidades relacionadas a Captain e Jasmine, incluindo ferramentas, serviços LLM, integrações WhatsApp e ajustes de configuração.
This commit is contained in:
parent
a392d81f06
commit
2672d21136
@ -1,3 +1,9 @@
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Exclude
|
||||
|
||||
plugins:
|
||||
- rubocop-performance
|
||||
- rubocop-rails
|
||||
@ -23,6 +29,7 @@ Metrics/MethodLength:
|
||||
Max: 19
|
||||
Exclude:
|
||||
- 'enterprise/lib/captain/agent.rb'
|
||||
- 'enterprise/app/services/captain/llm/system_prompts_service.rb'
|
||||
|
||||
RSpec/ExampleLength:
|
||||
Max: 50
|
||||
@ -230,6 +237,7 @@ AllCops:
|
||||
- 'tmp/**/*'
|
||||
- 'storage/**/*'
|
||||
- 'db/migrate/20230426130150_init_schema.rb'
|
||||
- 'reference_chatwoot/**/*'
|
||||
|
||||
FactoryBot/SyntaxMethods:
|
||||
Enabled: false
|
||||
|
||||
331
.rubocop_todo.yml
Normal file
331
.rubocop_todo.yml
Normal file
@ -0,0 +1,331 @@
|
||||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config --exclude-limit 10000`
|
||||
# on 2026-01-25 12:20:43 UTC using RuboCop version 1.75.6.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 2
|
||||
Chatwoot/AttachmentDownload:
|
||||
Exclude:
|
||||
- 'app/services/whatsapp/providers/wuzapi_service.rb'
|
||||
- 'debug_media.rb'
|
||||
|
||||
# Offense count: 32
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Exclude:
|
||||
- 'app/jobs/conversations/cluster_job.rb'
|
||||
- 'app/models/channel/whatsapp.rb'
|
||||
- 'app/services/captain/reservations/sync_service.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi_service.rb'
|
||||
- 'enterprise/app/jobs/captain/conversation/debounce_response_job.rb'
|
||||
- 'enterprise/app/services/captain/llm/system_prompts_service.rb'
|
||||
- 'enterprise/app/services/captain/tools/check_availability_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/generate_pix_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/status_suites_tool.rb'
|
||||
- 'enterprise/app/services/enterprise/message_templates/hook_execution_service.rb'
|
||||
- 'enterprise/lib/captain/tools/scenario_delegator_tool.rb'
|
||||
- 'lib/wuzapi/client.rb'
|
||||
- 'scripts/debug_faq_search.rb'
|
||||
- 'spec/factories/contacts.rb'
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
||||
Lint/DuplicateBranch:
|
||||
Exclude:
|
||||
- 'app/services/whatsapp/providers/wuzapi/payload_parser.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi_service.rb'
|
||||
- 'enterprise/app/services/captain/assistant/agent_runner_service.rb'
|
||||
|
||||
# Offense count: 2
|
||||
Lint/DuplicateMethods:
|
||||
Exclude:
|
||||
- 'app/services/jasmine/semantic_search_service.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: MaximumRangeSize.
|
||||
Lint/MissingCopEnableDirective:
|
||||
Exclude:
|
||||
- 'db/migrate/20260114100000_create_captain_inbox_automations.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Lint/NonLocalExitFromIterator:
|
||||
Exclude:
|
||||
- 'app/services/whatsapp/incoming_message_service_helpers.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Lint/ShadowedException:
|
||||
Exclude:
|
||||
- 'enterprise/app/services/captain/llm/paginated_faq_generator_service.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
|
||||
# NotImplementedExceptions: NotImplementedError
|
||||
Lint/UnusedMethodArgument:
|
||||
Exclude:
|
||||
- 'enterprise/app/services/llm/base_ai_service.rb'
|
||||
|
||||
# Offense count: 63
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- 'app/jobs/conversations/auto_label_job.rb'
|
||||
- 'app/jobs/conversations/resolution_job.rb'
|
||||
- 'app/jobs/webhooks/whatsapp_events_job.rb'
|
||||
- 'app/listeners/jasmine_listener.rb'
|
||||
- 'app/models/channel/whatsapp.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'app/services/captain/reservations/sync_service.rb'
|
||||
- 'app/services/crm_insights/update_service.rb'
|
||||
- 'app/services/jasmine/brain_service.rb'
|
||||
- 'app/services/jasmine/semantic_search_service.rb'
|
||||
- 'app/services/jasmine/tool_runner.rb'
|
||||
- 'app/services/whatsapp/decryption_service.rb'
|
||||
- 'app/services/whatsapp/incoming_message_wuzapi_service.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi/payload_parser.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi_service.rb'
|
||||
- 'config/initializers/fix_null_message_crash.rb'
|
||||
- 'db/migrate/20251227054034_create_jasmine_tables.rb'
|
||||
- 'db/migrate/20260110193000_fix_status_suites_headers.rb'
|
||||
- 'debug_token.rb'
|
||||
- 'enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb'
|
||||
- 'enterprise/app/controllers/api/v1/accounts/captain/reservations_controller.rb'
|
||||
- 'enterprise/app/controllers/api/v1/accounts/captain/tools_controller.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/reservations_controller.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/webhooks_controller.rb'
|
||||
- 'enterprise/app/jobs/captain/conversation/response_builder_job.rb'
|
||||
- 'enterprise/app/models/captain/assistant.rb'
|
||||
- 'enterprise/app/services/captain/assistant/agent_runner_service.rb'
|
||||
- 'enterprise/app/services/captain/inter/auth_service.rb'
|
||||
- 'enterprise/app/services/captain/inter/cob_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/assistant_chat_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/jasmine_brain.rb'
|
||||
- 'enterprise/app/services/captain/reminders/create_service.rb'
|
||||
- 'enterprise/app/services/captain/reservations/create_service.rb'
|
||||
- 'enterprise/app/services/captain/tools/check_availability_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/generate_pix_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/react_to_message_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/status_suites_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/suite_watchdog_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/update_contact_tool.rb'
|
||||
- 'enterprise/lib/captain/tools/faq_lookup_tool.rb'
|
||||
- 'enterprise/lib/captain/tools/http_tool.rb'
|
||||
- 'enterprise/lib/captain/tools/scenario_delegator_tool.rb'
|
||||
- 'lib/wuzapi/client.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
|
||||
# AllowedMethods: refine
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- 'db/migrate/20260110193000_fix_status_suites_headers.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/reservations_controller.rb'
|
||||
- 'enterprise/app/models/concerns/captain_tools_helpers.rb'
|
||||
- 'script/test_auto_resolve_inbox.rb'
|
||||
- 'seed_captain_tools.rb'
|
||||
- 'seed_jasmine_hotel_v2.rb'
|
||||
|
||||
# Offense count: 11
|
||||
# Configuration parameters: CountComments, Max, CountAsOne.
|
||||
Metrics/ClassLength:
|
||||
Exclude:
|
||||
- 'app/models/channel/whatsapp.rb'
|
||||
- 'app/services/crm_insights/update_service.rb'
|
||||
- 'app/services/whatsapp/incoming_message_wuzapi_service.rb'
|
||||
- 'enterprise/app/jobs/captain/conversation/response_builder_job.rb'
|
||||
- 'enterprise/app/services/captain/assistant/agent_runner_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/assistant_chat_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/jasmine_brain.rb'
|
||||
- 'enterprise/app/services/captain/tools/check_availability_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/tool_runner.rb'
|
||||
- 'enterprise/lib/captain/tools/scenario_delegator_tool.rb'
|
||||
|
||||
# Offense count: 67
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 39
|
||||
|
||||
# Offense count: 78
|
||||
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
||||
Metrics/MethodLength:
|
||||
Exclude:
|
||||
- 'app/jobs/conversations/auto_label_job.rb'
|
||||
- 'app/jobs/conversations/cluster_job.rb'
|
||||
- 'app/jobs/jasmine/response_job.rb'
|
||||
- 'app/listeners/jasmine_listener.rb'
|
||||
- 'app/models/channel/whatsapp.rb'
|
||||
- 'app/services/captain/inter_service.rb'
|
||||
- 'app/services/captain/reservations/sync_service.rb'
|
||||
- 'app/services/crm_insights/update_service.rb'
|
||||
- 'app/services/jasmine/brain_service.rb'
|
||||
- 'app/services/jasmine/embedding_service.rb'
|
||||
- 'app/services/jasmine/semantic_search_service.rb'
|
||||
- 'app/services/jasmine/tool_runner.rb'
|
||||
- 'app/services/jasmine/vision_service.rb'
|
||||
- 'app/services/whatsapp/decryption_service.rb'
|
||||
- 'app/services/whatsapp/incoming_message_service_helpers.rb'
|
||||
- 'app/services/whatsapp/incoming_message_wuzapi_service.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi/payload_parser.rb'
|
||||
- 'app/services/whatsapp/providers/wuzapi_service.rb'
|
||||
- 'db/migrate/20251227054034_create_jasmine_tables.rb'
|
||||
- 'db/migrate/20260104150000_add_crm_insights_history_fields.rb'
|
||||
- 'db/migrate/20260110193000_fix_status_suites_headers.rb'
|
||||
- 'db/migrate/20260114090200_create_captain_reminders.rb'
|
||||
- 'debug_token.rb'
|
||||
- 'enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb'
|
||||
- 'enterprise/app/controllers/api/v1/accounts/captain/scenarios_controller.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/reservations_controller.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/webhooks_controller.rb'
|
||||
- 'enterprise/app/jobs/captain/conversation/response_builder_job.rb'
|
||||
- 'enterprise/app/jobs/captain/intent_classification_job.rb'
|
||||
- 'enterprise/app/services/captain/assistant/agent_runner_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/assistant_chat_service.rb'
|
||||
- 'enterprise/app/services/captain/llm/jasmine_brain.rb'
|
||||
- 'enterprise/app/services/captain/llm/system_prompts_service.rb'
|
||||
- 'enterprise/app/services/captain/reminders/create_service.rb'
|
||||
- 'enterprise/app/services/captain/reservations/create_service.rb'
|
||||
- 'enterprise/app/services/captain/tools/check_availability_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/generate_pix_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/react_to_message_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/reminder_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/suite_watchdog_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/update_contact_tool.rb'
|
||||
- 'enterprise/app/services/captain/webhook_sender_service.rb'
|
||||
- 'enterprise/app/services/captain/whatsapp_notification_service.rb'
|
||||
- 'enterprise/lib/captain/tools/faq_lookup_tool.rb'
|
||||
- 'enterprise/lib/captain/tools/scenario_delegator_tool.rb'
|
||||
- 'lib/wuzapi/client.rb'
|
||||
- 'scripts/force_create_captain_tables.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: CountComments, CountAsOne.
|
||||
Metrics/ModuleLength:
|
||||
Max: 132
|
||||
|
||||
# Offense count: 4
|
||||
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
||||
Metrics/ParameterLists:
|
||||
Max: 7
|
||||
|
||||
# Offense count: 46
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 40
|
||||
|
||||
# Offense count: 2
|
||||
Naming/AccessorMethodName:
|
||||
Exclude:
|
||||
- 'app/services/captain/inter_service.rb'
|
||||
- 'app/services/jasmine/vision_service.rb'
|
||||
|
||||
# Offense count: 7
|
||||
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
||||
# AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
|
||||
Naming/MethodParameterName:
|
||||
Exclude:
|
||||
- 'app/services/whatsapp/decryption_service.rb'
|
||||
- 'enterprise/app/controllers/public/api/v1/captain/reservations_controller.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: MinSize.
|
||||
Performance/CollectionLiteralInLoop:
|
||||
Exclude:
|
||||
- 'db/migrate/20260110193000_fix_status_suites_headers.rb'
|
||||
|
||||
# Offense count: 1
|
||||
Rails/AfterCommitOverride:
|
||||
Exclude:
|
||||
- 'app/models/channel/whatsapp.rb'
|
||||
|
||||
# Offense count: 5
|
||||
# Configuration parameters: Include.
|
||||
# Include: **/app/models/**/*.rb
|
||||
Rails/HasManyOrHasOneDependent:
|
||||
Exclude:
|
||||
- 'enterprise/app/models/captain/brand.rb'
|
||||
- 'enterprise/app/models/captain/unit.rb'
|
||||
|
||||
# Offense count: 17
|
||||
# Configuration parameters: IgnoreScopes, Include.
|
||||
# Include: **/app/models/**/*.rb
|
||||
Rails/InverseOf:
|
||||
Exclude:
|
||||
- 'app/models/captain/unit.rb'
|
||||
- 'app/models/captain_assistant.rb'
|
||||
- 'enterprise/app/models/captain/brand.rb'
|
||||
- 'enterprise/app/models/captain/pricing.rb'
|
||||
- 'enterprise/app/models/captain/pricing_inbox.rb'
|
||||
- 'enterprise/app/models/captain/reservation.rb'
|
||||
- 'enterprise/app/models/captain/unit.rb'
|
||||
- 'enterprise/app/models/captain_inbox.rb'
|
||||
|
||||
# Offense count: 9
|
||||
# Configuration parameters: ForbiddenMethods, AllowedMethods.
|
||||
# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all
|
||||
Rails/SkipsModelValidations:
|
||||
Exclude:
|
||||
- 'app/services/jasmine/tool_runner.rb'
|
||||
- 'enterprise/app/jobs/captain/documents/response_builder_job.rb'
|
||||
- 'enterprise/app/services/captain/tools/create_reservation_intent_tool.rb'
|
||||
- 'enterprise/app/services/captain/tools/generate_pix_tool.rb'
|
||||
- 'promote_super_admin.rb'
|
||||
- 'script/test_auto_resolve_inbox.rb'
|
||||
|
||||
# Offense count: 8
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/**/*.rb
|
||||
Rails/ThreeStateBooleanColumn:
|
||||
Exclude:
|
||||
- 'db/migrate/20240523120000_create_captain_configurations.rb'
|
||||
- 'db/migrate/20251227054034_create_jasmine_tables.rb'
|
||||
- 'db/migrate/20251227201733_create_captain_tool_configs.rb'
|
||||
- 'db/migrate/20260114101004_create_captain_extras.rb'
|
||||
- 'db/migrate/20260120141736_add_message_signature_enabled_to_inboxes.rb'
|
||||
- 'db/migrate/20260121165034_add_plug_play_to_captain_units.rb'
|
||||
|
||||
# Offense count: 2
|
||||
# Configuration parameters: TransactionMethods.
|
||||
Rails/TransactionExitStatement:
|
||||
Exclude:
|
||||
- 'app/services/jasmine/embedding_service.rb'
|
||||
- 'app/services/whatsapp/incoming_message_wuzapi_service.rb'
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: Include.
|
||||
# Include: **/app/models/**/*.rb
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Exclude:
|
||||
- 'app/models/jasmine/inbox_collection.rb'
|
||||
|
||||
# Offense count: 3
|
||||
# Configuration parameters: MinBranchesCount.
|
||||
Style/HashLikeCase:
|
||||
Exclude:
|
||||
- 'app/services/whatsapp/providers/wuzapi/payload_parser.rb'
|
||||
- 'enterprise/app/jobs/captain/conversation/response_builder_job.rb'
|
||||
|
||||
# Offense count: 4
|
||||
Style/OneClassPerFile:
|
||||
Exclude:
|
||||
- 'app/services/jasmine/brain_service.rb'
|
||||
|
||||
# Offense count: 8
|
||||
UseFromEmail:
|
||||
Exclude:
|
||||
- 'app/services/captain/reservations/sync_service.rb'
|
||||
- 'clear_chat_history.rb'
|
||||
- 'debug_auth.rb'
|
||||
- 'debug_token.rb'
|
||||
- 'force_reset.rb'
|
||||
- 'promote_super_admin.rb'
|
||||
- 'setup_docker_env.rb'
|
||||
@ -26,18 +26,10 @@ class Api::V1::Accounts::Inboxes::WuzapiController < Api::V1::Accounts::BaseCont
|
||||
status = status_data['status'] || status_data['state'] || status_data
|
||||
Rails.logger.info "Wuzapi Connect/QR Flow - Current Status: #{status}"
|
||||
|
||||
if %w[CONNECTED inChat success].include?(status)
|
||||
Rails.logger.info 'Wuzapi is already connected. Skipping QR.'
|
||||
render json: { qrcode: nil, status: 'CONNECTED', message: 'Already connected' }
|
||||
return
|
||||
end
|
||||
return if already_connected?(status)
|
||||
|
||||
qr_data = client.get_qr_code(user_token)
|
||||
Rails.logger.info "Wuzapi QR Data Response keys: #{begin
|
||||
qr_data.keys
|
||||
rescue StandardError
|
||||
'nil'
|
||||
end}"
|
||||
log_qr_data_keys(qr_data)
|
||||
render json: qr_data
|
||||
rescue Wuzapi::Client::Error => e
|
||||
Rails.logger.error "Wuzapi QR Error: #{e.message}"
|
||||
@ -118,8 +110,24 @@ class Api::V1::Accounts::Inboxes::WuzapiController < Api::V1::Accounts::BaseCont
|
||||
Rails.logger.error "Wuzapi Token Missing for Inbox #{@inbox.id}"
|
||||
raise 'Token Wuzapi ausente; reprovisionar usuário'
|
||||
else
|
||||
Rails.logger.info "Wuzapi Request using Token (last 6): ******#{token.to_s[-6..-1]}"
|
||||
Rails.logger.info "Wuzapi Request using Token (last 6): ******#{token.to_s[-6..]}"
|
||||
end
|
||||
token
|
||||
end
|
||||
|
||||
def already_connected?(status)
|
||||
if %w[CONNECTED inChat success].include?(status)
|
||||
Rails.logger.info 'Wuzapi is already connected. Skipping QR.'
|
||||
render json: { qrcode: nil, status: 'CONNECTED', message: 'Already connected' }
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def log_qr_data_keys(qr_data)
|
||||
Rails.logger.info "Wuzapi QR Data Response keys: #{qr_data.keys}"
|
||||
rescue StandardError
|
||||
Rails.logger.info 'Wuzapi QR Data Response keys: nil'
|
||||
end
|
||||
end
|
||||
|
||||
@ -54,6 +54,6 @@ class Api::V1::Accounts::Integrations::LlmModelsController < Api::V1::Accounts::
|
||||
Rails.logger.error(
|
||||
"[LLM][ModelTest] Failed to persist model test results hook_id=#{hook.id} errors=#{hook.errors.full_messages.join(', ')}"
|
||||
)
|
||||
hook.update_columns(settings: settings)
|
||||
hook.update_columns(settings: settings) # rubocop:disable Rails/SkipsModelValidations
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Jasmine
|
||||
class CollectionsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Jasmine::CollectionsController < Api::V1::Accounts::BaseController
|
||||
before_action :find_collection, only: [:destroy]
|
||||
|
||||
def index
|
||||
@ -44,8 +40,3 @@ module Api
|
||||
params.require(:collection).permit(:name, :description, :visibility, :owner_inbox_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Jasmine
|
||||
class DocumentsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Jasmine::DocumentsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_collection
|
||||
|
||||
def index
|
||||
@ -36,7 +32,3 @@ module Api
|
||||
params.require(:document).permit(:title, :content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Jasmine
|
||||
class InboxCollectionsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Jasmine::InboxCollectionsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox
|
||||
|
||||
def index
|
||||
@ -37,7 +33,3 @@ module Api
|
||||
@inbox = Current.account.inboxes.find(params[:inbox_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Jasmine
|
||||
class InboxConfigsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Jasmine::InboxConfigsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox
|
||||
|
||||
def show
|
||||
@ -30,7 +26,3 @@ module Api
|
||||
params.require(:inbox_config).permit(:name, :system_prompt, :is_enabled, :mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Public
|
||||
module Api
|
||||
module V1
|
||||
module Captain
|
||||
class PaymentsController < ApplicationController
|
||||
class Public::Api::V1::Captain::PaymentsController < ApplicationController
|
||||
layout false
|
||||
skip_before_action :authenticate_user!, raise: false
|
||||
skip_before_action :check_current_user_is_active, raise: false
|
||||
@ -19,7 +15,3 @@ module Public
|
||||
# It should be a Captain::PixCharge model
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -37,9 +37,22 @@ class Webhooks::WhatsappController < ActionController::API
|
||||
# This prevents ANY binary data from leaking into JSON serialization
|
||||
def sanitize_payload_for_sidekiq
|
||||
raw = params.to_unsafe_hash
|
||||
clean_payload = build_base_payload(raw)
|
||||
|
||||
# Build a completely NEW payload with only safe fields
|
||||
clean_payload = {
|
||||
clean_payload['event'] = build_clean_event(raw['event']) if raw['event'].is_a?(Hash)
|
||||
|
||||
if raw['whatsapp'].is_a?(Hash)
|
||||
clean_payload['whatsapp'] = { 'event' => clean_payload['event'] }.merge(
|
||||
raw['whatsapp'].slice('type', 'state', 'instanceName', 'userID')
|
||||
)
|
||||
end
|
||||
|
||||
Rails.logger.info 'WuzAPI: Payload sanitized (WHITELIST mode)'
|
||||
deep_force_utf8(clean_payload)
|
||||
end
|
||||
|
||||
def build_base_payload(raw)
|
||||
{
|
||||
'type' => raw['type'],
|
||||
'state' => raw['state'],
|
||||
'instanceName' => raw['instanceName'],
|
||||
@ -48,14 +61,13 @@ class Webhooks::WhatsappController < ActionController::API
|
||||
'action' => raw['action'],
|
||||
'phone_number' => raw['phone_number']
|
||||
}
|
||||
end
|
||||
|
||||
# Only copy safe event fields
|
||||
if raw['event'].is_a?(Hash)
|
||||
def build_clean_event(raw_event)
|
||||
clean_event = {}
|
||||
|
||||
# Info fields (all safe strings/ids)
|
||||
if raw['event']['Info'].is_a?(Hash)
|
||||
clean_event['Info'] = raw['event']['Info'].slice(
|
||||
if raw_event['Info'].is_a?(Hash)
|
||||
clean_event['Info'] = raw_event['Info'].slice(
|
||||
'ID', 'Type', 'MediaType', 'Chat', 'Sender', 'SenderAlt', 'RecipientAlt', 'IsFromMe', 'IsGroup',
|
||||
'Timestamp', 'PushName', 'MessageSource'
|
||||
)
|
||||
@ -64,98 +76,76 @@ class Webhooks::WhatsappController < ActionController::API
|
||||
# Safe event metadata
|
||||
%w[Chat Sender IsFromMe IsGroup Timestamp AddressingMode BroadcastListOwner
|
||||
BroadcastRecipients RecipientAlt SenderAlt MessageIDs MessageSender].each do |key|
|
||||
clean_event[key] = raw['event'][key] if raw['event'].key?(key)
|
||||
clean_event[key] = raw_event[key] if raw_event.key?(key)
|
||||
end
|
||||
|
||||
# Message content - WHITELIST only safe fields
|
||||
if raw['event']['Message'].is_a?(Hash)
|
||||
msg = raw['event']['Message']
|
||||
clean_msg = {}
|
||||
if raw_event['Message'].is_a?(Hash)
|
||||
clean_msg = build_clean_message(raw_event['Message'])
|
||||
clean_event['Message'] = clean_msg unless clean_msg.empty?
|
||||
end
|
||||
|
||||
# Text messages
|
||||
clean_event
|
||||
end
|
||||
|
||||
def build_clean_message(msg)
|
||||
clean_msg = {}
|
||||
clean_msg['conversation'] = msg['conversation'] if msg['conversation'].is_a?(String)
|
||||
|
||||
if msg['extendedTextMessage'].is_a?(Hash)
|
||||
clean_msg['extendedTextMessage'] = {
|
||||
'text' => msg['extendedTextMessage']['text']
|
||||
}
|
||||
# Only copy contextInfo if it doesn't have quotedMessage with binaries
|
||||
# Only copy contextInfo if it doesn't have quotedMessage with binaries
|
||||
if msg['extendedTextMessage']['contextInfo'].is_a?(Hash)
|
||||
ctx = msg['extendedTextMessage']['contextInfo']
|
||||
clean_msg['extendedTextMessage']['contextInfo'] = {
|
||||
clean_msg.merge!(clean_extended_text_message(msg['extendedTextMessage']))
|
||||
clean_msg.merge!(clean_media_message(msg, 'imageMessage'))
|
||||
clean_msg.merge!(clean_media_message(msg, 'videoMessage'))
|
||||
clean_msg.merge!(clean_media_message(msg, 'audioMessage'))
|
||||
clean_msg.merge!(clean_document_message(msg['documentMessage']))
|
||||
|
||||
clean_msg
|
||||
end
|
||||
|
||||
def clean_extended_text_message(ext_msg)
|
||||
return {} unless ext_msg.is_a?(Hash)
|
||||
|
||||
result = { 'extendedTextMessage' => { 'text' => ext_msg['text'] } }
|
||||
|
||||
result['extendedTextMessage']['contextInfo'] = clean_context_info(ext_msg['contextInfo']) if ext_msg['contextInfo'].is_a?(Hash)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def clean_context_info(ctx)
|
||||
{
|
||||
'stanzaID' => ctx['stanzaID'] || ctx['stanzaId'],
|
||||
'participant' => ctx['participant']
|
||||
}.compact
|
||||
end
|
||||
|
||||
def clean_media_message(msg, type)
|
||||
media = msg[type]
|
||||
return {} unless media.is_a?(Hash)
|
||||
|
||||
clean_data = {
|
||||
'URL' => media['URL'] || media['url'],
|
||||
'directPath' => media['directPath'],
|
||||
'mediaKey' => media['mediaKey'],
|
||||
'fileEncSha256' => media['fileEncSha256'] || media['fileEncSHA256'],
|
||||
'fileSha256' => media['fileSha256'] || media['fileSHA256'],
|
||||
'fileLength' => media['fileLength'],
|
||||
'mimetype' => media['mimetype'],
|
||||
'seconds' => media['seconds'],
|
||||
'caption' => media['caption'],
|
||||
'ptt' => media['ptt'],
|
||||
'width' => media['width'],
|
||||
'height' => media['height']
|
||||
}
|
||||
|
||||
clean_data['contextInfo'] = clean_context_info(media['contextInfo']) if media['contextInfo'].is_a?(Hash)
|
||||
|
||||
{ type => clean_data.compact }
|
||||
end
|
||||
|
||||
# Image messages - ONLY safe metadata, NO binaries
|
||||
if msg['imageMessage'].is_a?(Hash)
|
||||
img = msg['imageMessage']
|
||||
clean_msg['imageMessage'] = {
|
||||
'URL' => img['URL'] || img['url'],
|
||||
'directPath' => img['directPath'],
|
||||
'mediaKey' => img['mediaKey'],
|
||||
'fileEncSha256' => img['fileEncSha256'] || img['fileEncSHA256'],
|
||||
'fileSha256' => img['fileSha256'] || img['fileSHA256'],
|
||||
'fileLength' => img['fileLength'],
|
||||
'mimetype' => img['mimetype'],
|
||||
'width' => img['width'],
|
||||
'height' => img['height'],
|
||||
'caption' => img['caption'],
|
||||
'contextInfo' => {
|
||||
'stanzaID' => img.dig('contextInfo', 'stanzaID') || img.dig('contextInfo', 'stanzaId'),
|
||||
'participant' => img.dig('contextInfo', 'participant')
|
||||
}.compact
|
||||
}.compact
|
||||
# EXPLICITLY NO: JPEGThumbnail, scansSidecar, firstScanSidecar, etc
|
||||
end
|
||||
def clean_document_message(doc)
|
||||
return {} unless doc.is_a?(Hash)
|
||||
|
||||
# Video messages - ONLY safe metadata
|
||||
if msg['videoMessage'].is_a?(Hash)
|
||||
vid = msg['videoMessage']
|
||||
clean_msg['videoMessage'] = {
|
||||
'URL' => vid['URL'] || vid['url'],
|
||||
'directPath' => vid['directPath'],
|
||||
'mediaKey' => vid['mediaKey'],
|
||||
'fileEncSha256' => vid['fileEncSha256'] || vid['fileEncSHA256'],
|
||||
'fileSha256' => vid['fileSha256'] || vid['fileSHA256'],
|
||||
'fileLength' => vid['fileLength'],
|
||||
'mimetype' => vid['mimetype'],
|
||||
'seconds' => vid['seconds'],
|
||||
'caption' => vid['caption'],
|
||||
'contextInfo' => {
|
||||
'stanzaID' => vid.dig('contextInfo', 'stanzaID') || vid.dig('contextInfo', 'stanzaId'),
|
||||
'participant' => vid.dig('contextInfo', 'participant')
|
||||
}.compact
|
||||
}.compact
|
||||
end
|
||||
|
||||
# Audio messages
|
||||
if msg['audioMessage'].is_a?(Hash)
|
||||
aud = msg['audioMessage']
|
||||
clean_msg['audioMessage'] = {
|
||||
'URL' => aud['URL'] || aud['url'],
|
||||
'directPath' => aud['directPath'],
|
||||
'mediaKey' => aud['mediaKey'],
|
||||
'fileEncSha256' => aud['fileEncSha256'] || aud['fileEncSHA256'],
|
||||
'fileSha256' => aud['fileSha256'] || aud['fileSHA256'],
|
||||
'fileLength' => aud['fileLength'],
|
||||
'mimetype' => aud['mimetype'],
|
||||
'seconds' => aud['seconds'],
|
||||
'ptt' => aud['ptt'],
|
||||
'contextInfo' => {
|
||||
'stanzaID' => aud.dig('contextInfo', 'stanzaID') || aud.dig('contextInfo', 'stanzaId'),
|
||||
'participant' => aud.dig('contextInfo', 'participant')
|
||||
}.compact
|
||||
}.compact
|
||||
end
|
||||
|
||||
# Document messages
|
||||
if msg['documentMessage'].is_a?(Hash)
|
||||
doc = msg['documentMessage']
|
||||
clean_msg['documentMessage'] = {
|
||||
{
|
||||
'documentMessage' => {
|
||||
'URL' => doc['URL'] || doc['url'],
|
||||
'directPath' => doc['directPath'],
|
||||
'mediaKey' => doc['mediaKey'],
|
||||
@ -166,24 +156,7 @@ class Webhooks::WhatsappController < ActionController::API
|
||||
'fileName' => doc['fileName'],
|
||||
'title' => doc['title']
|
||||
}.compact
|
||||
end
|
||||
|
||||
clean_event['Message'] = clean_msg unless clean_msg.empty?
|
||||
end
|
||||
|
||||
clean_payload['event'] = clean_event
|
||||
end
|
||||
|
||||
# Also copy whatsapp key if present (but sanitize it too)
|
||||
if raw['whatsapp'].is_a?(Hash)
|
||||
# Just reference the same clean event structure
|
||||
clean_payload['whatsapp'] = { 'event' => clean_payload['event'] }.merge(
|
||||
raw['whatsapp'].slice('type', 'state', 'instanceName', 'userID')
|
||||
)
|
||||
end
|
||||
|
||||
Rails.logger.info 'WuzAPI: Payload sanitized (WHITELIST mode)'
|
||||
deep_force_utf8(clean_payload)
|
||||
}
|
||||
end
|
||||
|
||||
def deep_force_utf8(obj)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
class Webhooks::WuzapiController < ActionController::Base
|
||||
class Webhooks::WuzapiController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
before_action :fetch_inbox
|
||||
before_action :verify_secret
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Conversations
|
||||
class AutoLabelJob < ApplicationJob
|
||||
class Conversations::AutoLabelJob < ApplicationJob
|
||||
queue_as :low_priority
|
||||
|
||||
TAXONOMY = {
|
||||
@ -17,7 +16,7 @@ module Conversations
|
||||
def perform(conversation_id)
|
||||
conversation = Conversation.find_by(id: conversation_id)
|
||||
return unless conversation
|
||||
return unless conversation.messages.count > 0
|
||||
return unless conversation.messages.count.positive?
|
||||
|
||||
# Evita re-classificar se já tiver alguma label de IA (opcional)
|
||||
# return if (conversation.label_list & TAXONOMY.keys).any?
|
||||
@ -131,4 +130,3 @@ module Conversations
|
||||
Rails.logger.info "[AutoLabelJob] Applied label #{label_name} and saved reason '#{question_summary}' to conversation #{conversation.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Conversations
|
||||
class ClusterJob < ApplicationJob
|
||||
class Conversations::ClusterJob < ApplicationJob
|
||||
queue_as :low_priority
|
||||
|
||||
def perform(account_id, days_back = 7)
|
||||
@ -9,7 +8,7 @@ module Conversations
|
||||
start_date = days_back.days.ago.beginning_of_day
|
||||
|
||||
# Labels que queremos agrupar (todas da taxonomia)
|
||||
Conversations::AutoLabelJob::TAXONOMY.keys.each do |label|
|
||||
Conversations::AutoLabelJob::TAXONOMY.each_key do |label|
|
||||
# Busca perguntas dessa categoria
|
||||
# Note: estamos queryng o campo JSONB additional_attributes
|
||||
account.conversations
|
||||
@ -83,4 +82,3 @@ module Conversations
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module CrmInsights
|
||||
class UpdateJob < ApplicationJob
|
||||
class CrmInsights::UpdateJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform(conversation_id, reason: nil)
|
||||
@ -14,4 +13,3 @@ module CrmInsights
|
||||
UpdateService.new(conversation: conversation, reason: reason).call
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Jasmine
|
||||
class ResponseJob < ApplicationJob
|
||||
class Jasmine::ResponseJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
retry_on StandardError, wait: :polynomially_longer, attempts: 2
|
||||
@ -58,4 +57,3 @@ module Jasmine
|
||||
Rails.logger.error "[Jasmine::ResponseJob] Failed to send response: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -25,10 +25,8 @@
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (captain_brand_id => captain_brands.id)
|
||||
#
|
||||
module Captain
|
||||
class Pricing < ApplicationRecord
|
||||
class Captain::Pricing < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :captain_brand, optional: true
|
||||
belongs_to :inbox, optional: true
|
||||
end
|
||||
end
|
||||
|
||||
@ -48,8 +48,7 @@
|
||||
# fk_rails_... (conversation_id => conversations.id)
|
||||
# fk_rails_... (inbox_id => inboxes.id)
|
||||
#
|
||||
module Captain
|
||||
class Reservation < ApplicationRecord
|
||||
class Captain::Reservation < ApplicationRecord
|
||||
self.table_name = 'captain_reservations'
|
||||
|
||||
belongs_to :account
|
||||
@ -83,4 +82,3 @@ module Captain
|
||||
where('check_in_at < ? AND check_out_at > ?', end_date, start_date)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@ -38,8 +38,7 @@
|
||||
# fk_rails_... (captain_brand_id => captain_brands.id)
|
||||
# fk_rails_... (inbox_id => inboxes.id)
|
||||
#
|
||||
module Captain
|
||||
class Unit < ApplicationRecord
|
||||
class Captain::Unit < ApplicationRecord
|
||||
self.table_name = 'captain_units'
|
||||
|
||||
belongs_to :account
|
||||
@ -57,4 +56,3 @@ module Captain
|
||||
|
||||
validates :name, presence: true
|
||||
end
|
||||
end
|
||||
|
||||
@ -197,7 +197,7 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
provider_config.delete('wuzapi_user_token')
|
||||
end
|
||||
|
||||
return unless provider_config['wuzapi_admin_token'].present?
|
||||
return if provider_config['wuzapi_admin_token'].blank?
|
||||
|
||||
self.wuzapi_admin_token = provider_config['wuzapi_admin_token']
|
||||
provider_config.delete('wuzapi_admin_token')
|
||||
@ -209,13 +209,13 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
|
||||
def perform_webhook_setup
|
||||
if provider == 'wuzapi'
|
||||
return unless inbox.present?
|
||||
return if inbox.blank?
|
||||
|
||||
base_url = provider_config['wuzapi_base_url']
|
||||
# Use encrypted token
|
||||
user_token = wuzapi_user_token
|
||||
|
||||
return unless user_token.present?
|
||||
return if user_token.blank?
|
||||
|
||||
# Construct Chatwoot Webhook URL
|
||||
# Using standard route: /webhooks/whatsapp/:phone_number for WuzAPI as per fix
|
||||
@ -286,7 +286,7 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
provider_config['wuzapi_user_id'] = result[:wuzapi_user_id]
|
||||
self.wuzapi_user_token = result[:wuzapi_user_token]
|
||||
|
||||
masked_token = result[:wuzapi_user_token].to_s[-4..-1]
|
||||
masked_token = result[:wuzapi_user_token].to_s[-4..]
|
||||
Rails.logger.info "Wuzapi User Provisioned. ID: #{result[:wuzapi_user_id]}, Token (last 4): ****#{masked_token}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -24,15 +24,14 @@
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (owner_inbox_id => inboxes.id)
|
||||
#
|
||||
module Jasmine
|
||||
class Collection < ApplicationRecord
|
||||
class Jasmine::Collection < ApplicationRecord
|
||||
self.table_name = 'jasmine_collections'
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :owner_inbox, class_name: 'Inbox', optional: true
|
||||
|
||||
has_many :inbox_collections, class_name: 'Jasmine::InboxCollection', foreign_key: :collection_id, dependent: :destroy
|
||||
has_many :documents, class_name: 'Jasmine::Document', foreign_key: :collection_id, dependent: :destroy
|
||||
has_many :inbox_collections, class_name: 'Jasmine::InboxCollection', dependent: :destroy
|
||||
has_many :documents, class_name: 'Jasmine::Document', dependent: :destroy
|
||||
|
||||
enum visibility: { private: 0, shared: 1, global: 2 }, _prefix: true
|
||||
|
||||
@ -43,9 +42,8 @@ module Jasmine
|
||||
private
|
||||
|
||||
def validate_owner_if_private
|
||||
if visibility_private? && owner_inbox_id.nil?
|
||||
return unless visibility_private? && owner_inbox_id.nil?
|
||||
|
||||
errors.add(:owner_inbox_id, 'must be present for private collections')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -25,13 +25,12 @@
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (collection_id => jasmine_collections.id)
|
||||
#
|
||||
module Jasmine
|
||||
class Document < ApplicationRecord
|
||||
class Jasmine::Document < ApplicationRecord
|
||||
self.table_name = 'jasmine_documents'
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :collection, class_name: 'Jasmine::Collection'
|
||||
has_many :chunks, class_name: 'Jasmine::DocumentChunk', foreign_key: :document_id, dependent: :delete_all
|
||||
has_many :chunks, class_name: 'Jasmine::DocumentChunk', dependent: :delete_all
|
||||
|
||||
enum status: { pending: 0, processing: 1, indexed: 2, failed: 3 }
|
||||
enum source_type: { manual: 0, upload: 1, url: 2, faq: 3 }
|
||||
@ -46,6 +45,7 @@ module Jasmine
|
||||
|
||||
def validate_account_consistency
|
||||
return if collection.nil?
|
||||
|
||||
errors.add(:base, 'Collection account mismatch') if collection.account_id != account_id
|
||||
end
|
||||
|
||||
@ -53,4 +53,3 @@ module Jasmine
|
||||
Jasmine::EmbedDocumentJob.perform_later(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -26,8 +26,7 @@
|
||||
# fk_rails_... (collection_id => jasmine_collections.id)
|
||||
# fk_rails_... (inbox_id => inboxes.id)
|
||||
#
|
||||
module Jasmine
|
||||
class InboxCollection < ApplicationRecord
|
||||
class Jasmine::InboxCollection < ApplicationRecord
|
||||
self.table_name = 'jasmine_inbox_collections'
|
||||
|
||||
belongs_to :account
|
||||
@ -53,9 +52,8 @@ module Jasmine
|
||||
def validate_visibility_rules
|
||||
return if collection.nil? || inbox.nil?
|
||||
|
||||
if collection.visibility_private? && collection.owner_inbox_id != inbox_id
|
||||
return unless collection.visibility_private? && collection.owner_inbox_id != inbox_id
|
||||
|
||||
errors.add(:base, 'Private collections can only be linked to their owner inbox')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -29,8 +29,7 @@
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (inbox_id => inboxes.id)
|
||||
#
|
||||
module Jasmine
|
||||
class InboxConfig < ApplicationRecord
|
||||
class Jasmine::InboxConfig < ApplicationRecord
|
||||
self.table_name = 'jasmine_inbox_settings'
|
||||
|
||||
belongs_to :account
|
||||
@ -44,7 +43,7 @@ module Jasmine
|
||||
|
||||
def validate_account_consistency
|
||||
return if inbox.nil?
|
||||
|
||||
errors.add(:base, 'Inbox account mismatch') if inbox.account_id != account_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -28,8 +28,7 @@
|
||||
# fk_rails_... (account_id => accounts.id)
|
||||
# fk_rails_... (inbox_id => inboxes.id)
|
||||
#
|
||||
module Jasmine
|
||||
class ToolConfig < ApplicationRecord
|
||||
class Jasmine::ToolConfig < ApplicationRecord
|
||||
self.table_name = 'jasmine_tool_configs'
|
||||
|
||||
belongs_to :account
|
||||
@ -68,4 +67,3 @@ module Jasmine
|
||||
DEFINITIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
module Assistant
|
||||
# Base module to fix NameError
|
||||
module Captain::Assistant
|
||||
# Base module to fix NameError
|
||||
end
|
||||
end
|
||||
|
||||
@ -4,8 +4,7 @@ require 'json'
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
|
||||
module Captain
|
||||
class InterService
|
||||
class Captain::InterService
|
||||
# Constants for API URLs
|
||||
AUTH_URL = 'https://cdpj.partners.bancointer.com.br/oauth/v2/token'.freeze
|
||||
PIX_URL = 'https://cdpj.partners.bancointer.com.br/pix/v2/cob'.freeze
|
||||
@ -118,4 +117,3 @@ module Captain
|
||||
http.request(req)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
module Captain
|
||||
module Reservations
|
||||
class SyncService
|
||||
PLUG_PLAY_API_BASE = 'https://oxpi.com.br/api/PlugPlay/api/Reserva'
|
||||
class Captain::Reservations::SyncService
|
||||
PLUG_PLAY_API_BASE = 'https://oxpi.com.br/api/PlugPlay/api/Reserva'.freeze
|
||||
|
||||
def initialize(unit)
|
||||
@unit = unit
|
||||
@ -171,5 +169,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module CrmInsights
|
||||
class ContactSessionCounter
|
||||
class CrmInsights::ContactSessionCounter
|
||||
WINDOW = 24.hours
|
||||
|
||||
def initialize(conversation)
|
||||
@ -28,4 +27,3 @@ module CrmInsights
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
module CrmInsights
|
||||
class GenerateService < Llm::BaseAiService
|
||||
DEFAULT_MODEL = 'gpt-4o-mini'
|
||||
class CrmInsights::GenerateService < Llm::BaseAiService
|
||||
DEFAULT_MODEL = 'gpt-4o-mini'.freeze
|
||||
|
||||
def initialize(conversation:, insight:, sessions_count:, last_contact_at:, from_message_id: nil, to_message_id: nil)
|
||||
super()
|
||||
@ -169,4 +168,3 @@ module CrmInsights
|
||||
"#{@from_message_id} ate #{@to_message_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module CrmInsights
|
||||
class UpdateService
|
||||
class CrmInsights::UpdateService
|
||||
def initialize(conversation:, reason: nil)
|
||||
@conversation = conversation
|
||||
@reason = reason
|
||||
@ -356,4 +355,3 @@ module CrmInsights
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Jasmine
|
||||
class BrainService
|
||||
class Jasmine::BrainService
|
||||
# Default intent keywords for hotel/motel business
|
||||
DEFAULT_INTENT_KEYWORDS = {
|
||||
price_question: %w[preço valor quanto custa fica tabela promoção pernoite diária],
|
||||
@ -283,4 +282,3 @@ module Jasmine
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Jasmine
|
||||
class EmbeddingService
|
||||
class Jasmine::EmbeddingService
|
||||
CHUNK_SIZE = 1000
|
||||
CHUNK_OVERLAP = 200
|
||||
EMBEDDING_DIMENSIONS = 1536 # OpenAI text-embedding-3-small dimensions
|
||||
@ -91,7 +90,7 @@ module Jasmine
|
||||
else
|
||||
# Fallback: Generate deterministic hash-based embedding for testing
|
||||
# This won't provide semantic search but allows the system to function
|
||||
Rails.logger.warn "OpenAI not configured, using fallback embedding for Jasmine"
|
||||
Rails.logger.warn 'OpenAI not configured, using fallback embedding for Jasmine'
|
||||
generate_fallback_embedding(text)
|
||||
end
|
||||
end
|
||||
@ -125,5 +124,3 @@ module Jasmine
|
||||
ENV.fetch('JASMINE_EMBEDDING_MODEL', 'text-embedding-3-small')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Jasmine
|
||||
class MediaAnalyzerService
|
||||
class Jasmine::MediaAnalyzerService
|
||||
attr_reader :message
|
||||
|
||||
def initialize(message:)
|
||||
@ -16,7 +15,7 @@ module Jasmine
|
||||
private
|
||||
|
||||
def analyze_audio
|
||||
message.attachments.where(file_type: :audio).each do |attachment|
|
||||
message.attachments.where(file_type: :audio).find_each do |attachment|
|
||||
next if attachment.meta&.dig('transcribed_text').present?
|
||||
|
||||
Rails.logger.info "[Jasmine::MediaAnalyzer] Transcribing audio for Attachment #{attachment.id}"
|
||||
@ -33,7 +32,7 @@ module Jasmine
|
||||
end
|
||||
|
||||
def analyze_images
|
||||
message.attachments.where(file_type: :image).each do |attachment|
|
||||
message.attachments.where(file_type: :image).find_each do |attachment|
|
||||
next if attachment.meta&.dig('description').present?
|
||||
|
||||
Rails.logger.info "[Jasmine::MediaAnalyzer] Analyzing image for Attachment #{attachment.id}"
|
||||
@ -50,4 +49,3 @@ module Jasmine
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Jasmine
|
||||
class SemanticSearchService
|
||||
class Jasmine::SemanticSearchService
|
||||
CANDIDATES_PER_PRIORITY = 50
|
||||
TOP_K_PER_PRIORITY = 10
|
||||
MAX_CHUNKS_PER_DOC = 2
|
||||
@ -175,4 +174,3 @@ module Jasmine
|
||||
vector.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
require 'rest-client'
|
||||
|
||||
module Jasmine
|
||||
class ToolRunner
|
||||
class Jasmine::ToolRunner
|
||||
attr_reader :inbox, :tool_key, :config
|
||||
|
||||
def initialize(inbox, tool_key)
|
||||
@ -13,7 +12,7 @@ module Jasmine
|
||||
def run
|
||||
definition = Jasmine::ToolConfig::DEFINITIONS[@tool_key]
|
||||
raise "Tool not found definition: #{@tool_key}" unless definition
|
||||
raise "Tool not configured or disabled" unless config&.is_enabled?
|
||||
raise 'Tool not configured or disabled' unless config&.is_enabled?
|
||||
|
||||
start_time = Time.current
|
||||
|
||||
@ -21,7 +20,7 @@ module Jasmine
|
||||
response = make_request(definition)
|
||||
duration = (Time.current - start_time) * 1000
|
||||
|
||||
success = (200..299).include?(response.code)
|
||||
success = (200..299).cover?(response.code)
|
||||
body = response.body.to_s
|
||||
|
||||
# Save stats
|
||||
@ -38,7 +37,11 @@ module Jasmine
|
||||
error_msg = e.message
|
||||
|
||||
# Try to parse body from error response if available
|
||||
error_body = e.response&.body rescue nil
|
||||
error_body = begin
|
||||
e.response&.body
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
error_msg = "#{error_msg} - #{error_body}" if error_body
|
||||
|
||||
sanitized_error = sanitize(error_msg)
|
||||
@ -97,8 +100,8 @@ module Jasmine
|
||||
end
|
||||
|
||||
def sanitize(text)
|
||||
return text unless @config&.plug_play_token.present?
|
||||
return text if @config&.plug_play_token.blank?
|
||||
|
||||
text.to_s.gsub(@config.plug_play_token, '****')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -3,8 +3,7 @@
|
||||
require 'openai'
|
||||
require 'base64'
|
||||
|
||||
module Jasmine
|
||||
class VisionService
|
||||
class Jasmine::VisionService
|
||||
attr_reader :attachment
|
||||
|
||||
def initialize(attachment:)
|
||||
@ -66,4 +65,3 @@ module Jasmine
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
module Llm
|
||||
class BaseAiService
|
||||
class Llm::BaseAiService
|
||||
def initialize
|
||||
# Base initialization logic if needed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -15,7 +15,7 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
!@raw_message[:key][:fromMe]
|
||||
end
|
||||
|
||||
def jid_type # rubocop:disable Metrics/CyclomaticComplexity
|
||||
def jid_type
|
||||
jid = @raw_message[:key][:remoteJid]
|
||||
server = jid.split('@').last
|
||||
|
||||
@ -39,7 +39,7 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
end
|
||||
|
||||
def message_type # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength,Metrics/AbcSize
|
||||
def message_type # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
if msg.key?(:conversation) || msg.dig(:extendedTextMessage, :text).present?
|
||||
'text'
|
||||
@ -69,7 +69,7 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
end
|
||||
|
||||
def message_content # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength
|
||||
def message_content # rubocop:disable Metrics/MethodLength
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
case message_type
|
||||
when 'text'
|
||||
@ -96,7 +96,7 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_message_id # rubocop:disable Metrics/CyclomaticComplexity
|
||||
def reply_to_message_id
|
||||
msg = unwrap_ephemeral_message(@raw_message[:message])
|
||||
message_key = case message_type
|
||||
when 'text' then :extendedTextMessage
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
module Whatsapp::BaileysHandlers::MessagesUpsert # rubocop:disable Metrics/ModuleLength
|
||||
module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
include Whatsapp::BaileysHandlers::Helpers
|
||||
include BaileysHelper
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Whatsapp
|
||||
class DecryptionService
|
||||
class Whatsapp::DecryptionService
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
require 'net/http'
|
||||
@ -144,4 +143,3 @@ module Whatsapp
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,7 +137,7 @@ module Whatsapp::IncomingMessageServiceHelpers
|
||||
|
||||
# 2. Media Messages (Image, Video, Audio, Document, Sticker)
|
||||
[:imageMessage, :videoMessage, :audioMessage, :documentMessage, :stickerMessage].each do |media_key|
|
||||
next unless message.dig(media_key.to_s, 'contextInfo').present?
|
||||
next if message.dig(media_key.to_s, 'contextInfo').blank?
|
||||
|
||||
ctx = message[media_key.to_s]['contextInfo']
|
||||
@in_reply_to_external_id = ctx['stanzaID'] || ctx['stanzaId']
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Whatsapp
|
||||
class IncomingMessageWuzapiService < IncomingMessageBaseService
|
||||
class Whatsapp::IncomingMessageWuzapiService < IncomingMessageBaseService
|
||||
def perform
|
||||
parser = Whatsapp::Providers::Wuzapi::PayloadParser.new(params)
|
||||
Rails.logger.info "WuzapiService: Processing #{parser.message_type} from #{parser.sender_phone_number}"
|
||||
@ -247,4 +246,3 @@ module Whatsapp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
module Whatsapp
|
||||
module Providers
|
||||
module Wuzapi
|
||||
class PayloadParser
|
||||
class Whatsapp::Providers::Wuzapi::PayloadParser
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@ -82,7 +79,7 @@ module Whatsapp
|
||||
# 2. Media Types direct contextInfo
|
||||
[:imageMessage, :videoMessage, :audioMessage, :stickerMessage, :documentMessage].each do |key|
|
||||
ctx = msg.dig(key, :contextInfo)
|
||||
next unless ctx.present?
|
||||
next if ctx.blank?
|
||||
|
||||
Rails.logger.info "WuzAPI Reply Debug: Found #{key} contextInfo = #{ctx.inspect}"
|
||||
stanza = ctx[:stanzaID] || ctx[:stanzaId]
|
||||
@ -99,7 +96,7 @@ module Whatsapp
|
||||
end
|
||||
|
||||
# 4. Check for simple conversation with contextInfo (text reply without extendedTextMessage)
|
||||
if msg[:conversation].present? && msg.dig(:contextInfo).present?
|
||||
if msg[:conversation].present? && msg[:contextInfo].present?
|
||||
ctx = msg[:contextInfo]
|
||||
Rails.logger.info "WuzAPI Reply Debug: Found conversation contextInfo = #{ctx.inspect}"
|
||||
stanza = ctx[:stanzaID] || ctx[:stanzaId]
|
||||
@ -170,7 +167,7 @@ module Whatsapp
|
||||
return Time.current if timestamp_val.blank?
|
||||
|
||||
begin
|
||||
Time.parse(timestamp_val.to_s)
|
||||
Time.zone.parse(timestamp_val.to_s)
|
||||
rescue ArgumentError
|
||||
Time.current
|
||||
end
|
||||
@ -210,6 +207,3 @@ module Whatsapp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Whatsapp::Providers
|
||||
class WuzapiService < BaseService
|
||||
class Whatsapp::Providers::WuzapiService < BaseService
|
||||
attr_reader :whatsapp_channel
|
||||
|
||||
def initialize(whatsapp_channel:)
|
||||
@ -170,9 +169,8 @@ module Whatsapp::Providers
|
||||
target_message = message.conversation.messages.find_by(source_id: message.in_reply_to_external_id)
|
||||
end
|
||||
|
||||
return false unless target_message.present?
|
||||
return false if target_message.blank?
|
||||
|
||||
target_message.outgoing? || target_message.template?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -39,7 +39,7 @@ module Whatsapp::ZapiHandlers::ReceivedCallback # rubocop:disable Metrics/Module
|
||||
!@raw_message.key?(:notification)
|
||||
end
|
||||
|
||||
def message_type # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
||||
def message_type
|
||||
return 'text' if @raw_message.key?(:text)
|
||||
return 'reaction' if @raw_message.key?(:reaction)
|
||||
return 'audio' if @raw_message.key?(:audio)
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Wuzapi
|
||||
class ProvisioningService
|
||||
class Wuzapi::ProvisioningService
|
||||
def initialize(base_url, admin_token)
|
||||
@base_url = base_url
|
||||
@admin_token = admin_token
|
||||
@ -21,7 +20,8 @@ module Wuzapi
|
||||
}
|
||||
end
|
||||
|
||||
def setup_webhook(user_token, inbox_id, _webhook_secret) # [INTENTIONAL] reserved for signed webhooks
|
||||
# [INTENTIONAL] reserved for signed webhooks
|
||||
def setup_webhook(user_token, inbox_id, _webhook_secret)
|
||||
# Host logic needs to come from GlobalConfig or Rails.application.routes
|
||||
# Ideally passed in or resolved.
|
||||
base_host = ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
@ -32,4 +32,3 @@ module Wuzapi
|
||||
@client.set_webhook(user_token, webhook_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
Rails.application.configure do
|
||||
config.active_record.encryption.primary_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY', 'test_primary_key_must_be_32_bytes_long!!')
|
||||
config.active_record.encryption.deterministic_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY', 'test_deterministic_key_must_be_32_bytes!!')
|
||||
config.active_record.encryption.key_derivation_salt = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT', 'test_salt_must_be_unique_and_random')
|
||||
config.active_record.encryption.deterministic_key = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY',
|
||||
'test_deterministic_key_must_be_32_bytes!!')
|
||||
config.active_record.encryption.key_derivation_salt = ENV.fetch('ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT',
|
||||
'test_salt_must_be_unique_and_random')
|
||||
end
|
||||
|
||||
@ -15,7 +15,7 @@ Rails.application.config.after_initialize do
|
||||
Rails.logger.error "[RubyLLM] Init failed: #{e.class} #{e.message}"
|
||||
end
|
||||
Rails.logger.info "[RubyLLM] Configured with OPENAI_API_KEY: #{api_key[0..10]}..."
|
||||
puts "[RubyLLM] Configured with OPENAI_API_KEY: #{api_key[0..10]}..." # Log to stdout for rails runner visibility
|
||||
Rails.logger.debug { "[RubyLLM] Configured with OPENAI_API_KEY: #{api_key[0..10]}..." } # Log to stdout for rails runner visibility
|
||||
else
|
||||
Rails.logger.warn '[RubyLLM] No OPENAI_API_KEY found in environment'
|
||||
end
|
||||
|
||||
0
configure_captain_webhook.rb
Normal file → Executable file
0
configure_captain_webhook.rb
Normal file → Executable file
@ -8,4 +8,3 @@ class AddBrainFieldsToJasmineInboxSettings < ActiveRecord::Migration[7.1]
|
||||
add_column :jasmine_inbox_settings, :intent_keywords, :jsonb, default: {}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ class FixStatusSuitesHeaders < ActiveRecord::Migration[7.1]
|
||||
tools = Captain::CustomTool.where('endpoint_url ILIKE ? OR title ILIKE ?', '%/api/PlugPlay/api/SuitesStatus%', '%Status Suites%')
|
||||
|
||||
tools.each do |tool|
|
||||
puts "Processing tool: #{tool.title} (ID: #{tool.id})"
|
||||
Rails.logger.debug { "Processing tool: #{tool.title} (ID: #{tool.id})" }
|
||||
|
||||
updated = false
|
||||
new_auth_config = tool.auth_config || {}
|
||||
@ -20,7 +20,7 @@ class FixStatusSuitesHeaders < ActiveRecord::Migration[7.1]
|
||||
%w[PLUG-PLAY-ID PLUG-PLAY-TOKEN].each do |header_key|
|
||||
next unless query_params.key?(header_key)
|
||||
|
||||
puts " Found #{header_key} in URL query params. Moving to headers."
|
||||
Rails.logger.debug { " Found #{header_key} in URL query params. Moving to headers." }
|
||||
new_auth_config['headers'][header_key] = query_params[header_key]
|
||||
query_params.delete(header_key)
|
||||
updated = true
|
||||
@ -36,16 +36,16 @@ class FixStatusSuitesHeaders < ActiveRecord::Migration[7.1]
|
||||
if tool.param_schema.is_a?(Array)
|
||||
original_size = tool.param_schema.size
|
||||
tool.param_schema.reject! { |p| %w[PLUG-PLAY-ID PLUG-PLAY-TOKEN].include?(p['name']) }
|
||||
puts ' Removed params from param_schema.' if tool.param_schema.size < original_size
|
||||
Rails.logger.debug ' Removed params from param_schema.' if tool.param_schema.size < original_size
|
||||
end
|
||||
|
||||
tool.save!
|
||||
puts ' Tool updated successfully.'
|
||||
Rails.logger.debug ' Tool updated successfully.'
|
||||
else
|
||||
puts ' No keys found in URL query params. Manual update might be required for values.'
|
||||
Rails.logger.debug ' No keys found in URL query params. Manual update might be required for values.'
|
||||
end
|
||||
rescue URI::InvalidURIError # [INTENTIONAL] keep for future logging
|
||||
puts " Skipping invalid URI: #{tool.endpoint_url}"
|
||||
Rails.logger.debug { " Skipping invalid URI: #{tool.endpoint_url}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -6,7 +6,7 @@ if account
|
||||
features_config = YAML.safe_load(Rails.root.join('config/features.yml').read)
|
||||
|
||||
# Select features that should be enabled by default
|
||||
default_features = features_config.select { |f| f['enabled'] == true }.map { |f| f['name'] }
|
||||
default_features = features_config.select { |f| f['enabled'] == true }.pluck('name')
|
||||
|
||||
puts "Enabling default features: #{default_features.join(', ')}"
|
||||
|
||||
@ -16,5 +16,5 @@ if account
|
||||
puts "New feature flags: #{account.feature_flags}"
|
||||
puts "Inbox Management Enabled? #{account.feature_enabled?('inbox_management')}"
|
||||
else
|
||||
puts "Account not found!"
|
||||
puts 'Account not found!'
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Captain
|
||||
class BrandsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Captain::BrandsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_brand, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@ -46,7 +42,3 @@ module Api
|
||||
params.require(:brand).permit(:name, suite_categories: [], stay_durations: [], suite_images: {}, suite_keywords: {})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Captain
|
||||
class ConfigurationsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Captain::ConfigurationsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_config
|
||||
|
||||
def show
|
||||
@ -28,7 +24,3 @@ module Api
|
||||
params.require(:configuration).permit(:title, :subtitle, :primary_color, :secondary_color, :active, :logo_url, :phone_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Captain
|
||||
class ExtrasController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Captain::ExtrasController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_extra, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@ -46,7 +42,3 @@ module Api
|
||||
params.require(:extra).permit(:title, :description, :price, :category)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Captain
|
||||
class PricingsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Captain::PricingsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_pricing, only: [:show, :update, :destroy]
|
||||
|
||||
def index
|
||||
@ -61,7 +57,3 @@ module Api
|
||||
pricing.inboxes = inboxes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Captain
|
||||
class UnitsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Captain::UnitsController < Api::V1::Accounts::BaseController
|
||||
before_action :current_account
|
||||
before_action -> { check_authorization(::Captain::Assistant) }
|
||||
|
||||
@ -48,7 +44,3 @@ module Api
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
module Api
|
||||
module V1
|
||||
module Accounts
|
||||
module Inboxes
|
||||
module Captain
|
||||
class ReminderSettingsController < Api::V1::Accounts::BaseController
|
||||
class Api::V1::Accounts::Inboxes::Captain::ReminderSettingsController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox
|
||||
before_action :fetch_or_initialize_settings
|
||||
|
||||
@ -42,8 +37,3 @@ module Api
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Public
|
||||
module Api
|
||||
module V1
|
||||
module Captain
|
||||
class BookingAppController < ActionController::Base
|
||||
class Public::Api::V1::Captain::BookingAppController < ApplicationController
|
||||
layout false
|
||||
|
||||
def index
|
||||
@ -10,7 +6,3 @@ module Public
|
||||
render :index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Public
|
||||
module Api
|
||||
module V1
|
||||
module Captain
|
||||
class MasterDataController < ActionController::API
|
||||
class Public::Api::V1::Captain::MasterDataController < ActionController::API
|
||||
def show
|
||||
# Assuming account_id is passed or derived from domain
|
||||
account = Account.find(params[:account_id])
|
||||
@ -27,7 +23,3 @@ module Public
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Public
|
||||
module Api
|
||||
module V1
|
||||
module Captain
|
||||
class ReservationsController < ActionController::API
|
||||
class Public::Api::V1::Captain::ReservationsController < ActionController::API
|
||||
# Use basic rescue_from for generic error handling to capture 'Inbox not found' or similar if they bubble up
|
||||
rescue_from StandardError, with: :handle_standard_error
|
||||
|
||||
@ -166,7 +162,3 @@ module Public
|
||||
}, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
module Public
|
||||
module Api
|
||||
module V1
|
||||
module Captain
|
||||
class WebhooksController < ActionController::API
|
||||
class Public::Api::V1::Captain::WebhooksController < ActionController::API
|
||||
def inter_pix
|
||||
Rails.logger.info "[Captain::InterWebhook] 🔔 Received Webhook: #{params.to_unsafe_h}"
|
||||
|
||||
@ -48,7 +44,3 @@ module Public
|
||||
render json: { status: 'received' }, status: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -98,7 +98,8 @@ module Captain::ChatHelper
|
||||
end
|
||||
|
||||
def api_key
|
||||
raw_key = @assistant&.api_key.presence || @assistant&.config&.[]('openai_api_key').presence || ENV.fetch('OPENAI_API_KEY', nil) || ENV.fetch('GEMINI_API_KEY', nil)
|
||||
raw_key = @assistant&.api_key.presence || @assistant&.config&.[]('openai_api_key').presence || ENV.fetch('OPENAI_API_KEY',
|
||||
nil) || ENV.fetch('GEMINI_API_KEY', nil)
|
||||
return nil if raw_key.blank?
|
||||
|
||||
# Sanitize: Remove common accidental suffixes like image names or whitespace
|
||||
|
||||
@ -18,7 +18,7 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
trigger_media_analysis
|
||||
|
||||
Rails.logger.info "[ResponseBuilderJob] Captain V2 Enabled? #{captain_v2_enabled?}"
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.now}] ResponseBuilderJob: V2 Enabled? #{captain_v2_enabled?}" }
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.zone.now}] ResponseBuilderJob: V2 Enabled? #{captain_v2_enabled?}" }
|
||||
|
||||
if captain_v2_enabled?
|
||||
generate_response_with_v2
|
||||
@ -209,7 +209,7 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
elapsed_time = Time.zone.now - @start_time
|
||||
remaining_delay = target_delay - elapsed_time
|
||||
|
||||
sleep(remaining_delay) if remaining_delay > 0
|
||||
sleep(remaining_delay) if remaining_delay.positive?
|
||||
end
|
||||
|
||||
def fetch_new_incoming_messages
|
||||
@ -217,11 +217,11 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
all_messages = @conversation.messages.order(:created_at)
|
||||
|
||||
# Find the last message sent by the assistant (outgoing)
|
||||
last_outgoing_index = all_messages.rindex { |m| m.outgoing? }
|
||||
last_outgoing_index = all_messages.rindex(&:outgoing?)
|
||||
|
||||
potential_messages = if last_outgoing_index
|
||||
# Get all messages after the last outgoing one
|
||||
all_messages[(last_outgoing_index + 1)..-1] || []
|
||||
all_messages[(last_outgoing_index + 1)..] || []
|
||||
else
|
||||
# If no outgoing messages, use all messages
|
||||
all_messages
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class IntentClassificationJob < ApplicationJob
|
||||
class Captain::IntentClassificationJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
CATEGORIES = %w[
|
||||
@ -84,4 +83,3 @@ module Captain
|
||||
content&.strip&.downcase
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -108,7 +108,7 @@ class Captain::Assistant < ApplicationRecord
|
||||
next unless tool_class
|
||||
|
||||
# Avoid duplicates if tool is already added (e.g. hardcoded ones)
|
||||
next if tools.any? { |t| t.is_a?(tool_class) }
|
||||
next if tools.any?(tool_class)
|
||||
|
||||
tools << tool_class.new(self, conversation: conversation, user: user)
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class Configuration < ApplicationRecord
|
||||
class Captain::Configuration < ApplicationRecord
|
||||
self.table_name = 'captain_configurations'
|
||||
|
||||
belongs_to :account
|
||||
@ -7,4 +6,3 @@ module Captain
|
||||
validates :account_id, presence: true
|
||||
validates :title, presence: true
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class Reminder < ApplicationRecord
|
||||
class Captain::Reminder < ApplicationRecord
|
||||
self.table_name = 'captain_reminders'
|
||||
|
||||
belongs_to :account
|
||||
@ -18,4 +17,3 @@ module Captain
|
||||
scope :pending, -> { where(status: :pending) }
|
||||
scope :due, -> { pending.where('scheduled_at <= ?', Time.current) }
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class Reservation < ApplicationRecord
|
||||
class Captain::Reservation < ApplicationRecord
|
||||
self.table_name = 'captain_reservations'
|
||||
|
||||
belongs_to :account
|
||||
@ -34,4 +33,3 @@ module Captain
|
||||
|
||||
delegate :name, :email, :phone_number, to: :contact, prefix: true
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
module Captain
|
||||
class Suite < ApplicationRecord
|
||||
class Captain::Suite < ApplicationRecord
|
||||
self.table_name = 'captain_suites'
|
||||
belongs_to :account
|
||||
|
||||
validates :name, presence: true
|
||||
validates :category, presence: true
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class ToolConfig < ApplicationRecord
|
||||
class Captain::ToolConfig < ApplicationRecord
|
||||
self.table_name = 'captain_tool_configs'
|
||||
belongs_to :account
|
||||
belongs_to :inbox, optional: true
|
||||
@ -12,4 +11,3 @@ module Captain
|
||||
validates :tool_key, uniqueness: { scope: [:account_id, :inbox_id] }, if: -> { inbox_id.present? }
|
||||
validates :tool_key, uniqueness: { scope: [:account_id, :captain_assistant_id] }, if: -> { captain_assistant_id.present? }
|
||||
end
|
||||
end
|
||||
|
||||
@ -32,7 +32,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
|
||||
# [FEATURE] Short-circuit for thank you/emoji messages to ensure reaction tool usage
|
||||
Rails.logger.info "[Captain V2] Checking for reaction. Message: #{message_to_process.inspect}"
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.now}] AgentRunnerService: checking reaction for #{message_to_process.inspect}" }
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.zone.now}] AgentRunnerService: checking reaction for #{message_to_process.inspect}" }
|
||||
|
||||
reaction_response = check_and_react_to_message(message_to_process)
|
||||
return reaction_response if reaction_response
|
||||
@ -41,7 +41,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
runner = Agents::Runner.with_agents(*agents)
|
||||
runner = add_callbacks_to_runner(runner) if @callbacks.any?
|
||||
|
||||
puts "[DEBUG V2] Running with agents: #{agents.map(&:name).join(', ')}"
|
||||
Rails.logger.debug { "[DEBUG V2] Running with agents: #{agents.map(&:name).join(', ')}" }
|
||||
|
||||
# Use assistant's API key if present, otherwise fallback to global config
|
||||
result = with_assistant_api_key do
|
||||
@ -89,7 +89,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
end
|
||||
|
||||
Rails.logger.info "[Captain V2] Reaction Pre-Check: Text='#{text}' Category=#{matched_category}"
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.now}] AgentRunnerService: Text='#{text}' Category=#{matched_category}" }
|
||||
File.open('/tmp/v2_debug.log', 'a') { |f| f.puts "[#{Time.zone.now}] AgentRunnerService: Text='#{text}' Category=#{matched_category}" }
|
||||
|
||||
if matched_category
|
||||
Rails.logger.info "[Captain V2] Detected #{matched_category}. Executing ReactToMessageTool directly."
|
||||
@ -140,7 +140,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
# Remove the last user message from history because it will be passed as the main message to the runner
|
||||
last_user_index = message_history.rindex { |msg| msg[:role] == 'user' || msg[:role] == :user }
|
||||
filtered_history = if last_user_index
|
||||
message_history[0...last_user_index] + message_history[(last_user_index + 1)..-1]
|
||||
message_history[0...last_user_index] + message_history[(last_user_index + 1)..]
|
||||
else
|
||||
message_history
|
||||
end
|
||||
@ -328,7 +328,7 @@ class Captain::Assistant::AgentRunnerService
|
||||
def sanitize_global_api_key
|
||||
# Force sanitization of the global gem config just in case it's dirty
|
||||
raw_key = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_API_KEY')&.value.presence || ENV.fetch('OPENAI_API_KEY', nil)
|
||||
return unless raw_key.present?
|
||||
return if raw_key.blank?
|
||||
|
||||
sanitized_key = raw_key.to_s.gsub(/\.(png|jpg|jpeg|gif|webp|svg|@2x|@3x).*$/i, '').strip
|
||||
Agents.configure { |config| config.openai_api_key = sanitized_key }
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Captain
|
||||
class HandoffWebhookService
|
||||
class Captain::HandoffWebhookService
|
||||
def initialize(conversation:, assistant:, handoff_context: {})
|
||||
@conversation = conversation
|
||||
@assistant = assistant
|
||||
@ -85,7 +84,7 @@ module Captain
|
||||
end
|
||||
|
||||
def should_retry?
|
||||
(@assistant.handoff_webhook_config['retry_attempts'] || 0) > 0
|
||||
(@assistant.handoff_webhook_config['retry_attempts'] || 0).positive?
|
||||
end
|
||||
|
||||
def retry_if_needed(error)
|
||||
@ -102,4 +101,3 @@ module Captain
|
||||
ChatwootExceptionTracker.new(error, account: @conversation.account).capture_exception
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
require 'faraday'
|
||||
|
||||
module Captain
|
||||
module Inter
|
||||
class AuthService
|
||||
class Captain::Inter::AuthService
|
||||
API_BASE_URL = 'https://cdpj.partners.bancointer.com.br'.freeze
|
||||
TOKEN_URL = '/oauth/v2/token'.freeze
|
||||
|
||||
@ -57,5 +55,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Inter
|
||||
class CobService
|
||||
class Captain::Inter::CobService
|
||||
API_BASE_URL = 'https://cdpj.partners.bancointer.com.br'.freeze
|
||||
|
||||
def initialize(reservation)
|
||||
@ -9,7 +7,7 @@ module Captain
|
||||
end
|
||||
|
||||
def call
|
||||
raise 'Unit not configured for Pix' unless @unit.inter_pix_key.present?
|
||||
raise 'Unit not configured for Pix' if @unit.inter_pix_key.blank?
|
||||
|
||||
token = AuthService.new(@unit).token
|
||||
|
||||
@ -32,7 +30,7 @@ module Captain
|
||||
# [CRITICAL DEBUG] Log the ENTIRE response to see why it's being cut
|
||||
Rails.logger.info "[BANCO INTER] FULL RESPONSE: #{safe_body}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] BANCO INTER RAW BODY: #{safe_body}"
|
||||
f.puts "[#{Time.zone.now}] BANCO INTER RAW BODY: #{safe_body}"
|
||||
end
|
||||
|
||||
persist_charge(data)
|
||||
@ -86,5 +84,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -43,7 +43,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseAiService
|
||||
|
||||
# 3. Handle Tool Strategy
|
||||
if brain_decision.strategy == :execute_tool
|
||||
File.open(Rails.root.join('log/brain_debug.log'), 'a') { |f| f.puts "[#{Time.now}] BRAIN DECIDED: #{brain_decision.tool_key}" }
|
||||
File.open(Rails.root.join('log/brain_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] BRAIN DECIDED: #{brain_decision.tool_key}" }
|
||||
|
||||
inbox = @conversation.inbox
|
||||
|
||||
@ -57,7 +57,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseAiService
|
||||
additional_data: { message: additional_message, tool_input: brain_decision.tool_input }
|
||||
)
|
||||
|
||||
File.open(Rails.root.join('log/brain_debug.log'), 'a') { |f| f.puts "[#{Time.now}] RUNNER RESULT: #{runner_result.inspect}" }
|
||||
File.open(Rails.root.join('log/brain_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] RUNNER RESULT: #{runner_result.inspect}" }
|
||||
|
||||
return { 'response' => runner_result[:body][:message] } if runner_result[:fallback] && runner_result.dig(:body, :message).present?
|
||||
|
||||
|
||||
@ -59,6 +59,6 @@ class Captain::Llm::ContactIdentityService
|
||||
def normalize_name(name)
|
||||
parts = name.split
|
||||
parts.shift if parts.first&.downcase.in?(%w[a o])
|
||||
parts.map { |part| part.capitalize }.join(' ')
|
||||
parts.map(&:capitalize).join(' ')
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Llm
|
||||
class JasmineBrain
|
||||
class Captain::Llm::JasmineBrain
|
||||
Decision = Struct.new(:strategy, :tool_key, :reasoning, :tool_input, keyword_init: true)
|
||||
|
||||
def self.decide(assistant:, conversation:, message:, history:)
|
||||
@ -379,5 +377,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -86,7 +86,6 @@ class Captain::Llm::SystemPromptsService
|
||||
SYSTEM_PROMPT_MESSAGE
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def copilot_response_generator(product_name, available_tools, config = {})
|
||||
citation_guidelines = if config['feature_citation']
|
||||
<<~CITATION_TEXT
|
||||
@ -149,7 +148,6 @@ class Captain::Llm::SystemPromptsService
|
||||
#{available_tools}
|
||||
SYSTEM_PROMPT_MESSAGE
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def assistant_response_generator(assistant_name, product_name, config = {})
|
||||
json_instruction = <<~JSON_INSTRUCTION
|
||||
@ -258,7 +256,7 @@ class Captain::Llm::SystemPromptsService
|
||||
end
|
||||
|
||||
def assistant_prompt_from_blocks(blocks)
|
||||
Array(blocks).map do |block|
|
||||
Array(blocks).filter_map do |block|
|
||||
title = block['title'] || block[:title]
|
||||
content = block['content'] || block[:content]
|
||||
next if title.to_s.strip.empty? && content.to_s.strip.empty?
|
||||
@ -268,7 +266,7 @@ class Captain::Llm::SystemPromptsService
|
||||
else
|
||||
"[#{title}]\n#{content}".strip
|
||||
end
|
||||
end.compact.join("\n\n")
|
||||
end.join("\n\n")
|
||||
end
|
||||
|
||||
def paginated_faq_generator(start_page, end_page, language = 'english')
|
||||
|
||||
@ -19,8 +19,8 @@ class Captain::Reminders::CreateService
|
||||
inbox = conversation&.inbox || find_inbox
|
||||
contact_inbox = conversation&.contact_inbox || find_or_create_contact_inbox(inbox)
|
||||
|
||||
raise ArgumentError, 'Inbox not found' unless inbox.present?
|
||||
raise ArgumentError, 'Contact not found' unless contact_inbox&.contact.present?
|
||||
raise ArgumentError, 'Inbox not found' if inbox.blank?
|
||||
raise ArgumentError, 'Contact not found' if contact_inbox&.contact.blank?
|
||||
|
||||
schedule_time = parse_time(params[:scheduled_at])
|
||||
raise ArgumentError, 'Scheduled time is required' unless schedule_time
|
||||
|
||||
@ -32,7 +32,7 @@ class Captain::Reminders::Processor
|
||||
|
||||
def suite_available?
|
||||
assistant = inbox.captain_assistant
|
||||
return false unless assistant.present?
|
||||
return false if assistant.blank?
|
||||
|
||||
tool = Captain::Tools::StatusSuitesTool.new(assistant, conversation: conversation)
|
||||
result = tool.execute
|
||||
@ -49,8 +49,8 @@ class Captain::Reminders::Processor
|
||||
raise ArgumentError, 'Message content is required' if content.blank?
|
||||
|
||||
target_conversation = conversation || find_or_create_conversation
|
||||
raise ArgumentError, 'Conversation not found' unless target_conversation.present?
|
||||
raise ArgumentError, 'Assistant not configured for inbox' unless assistant.present?
|
||||
raise ArgumentError, 'Conversation not found' if target_conversation.blank?
|
||||
raise ArgumentError, 'Assistant not configured for inbox' if assistant.blank?
|
||||
|
||||
Current.executed_by = assistant
|
||||
Current.account = @reminder.account
|
||||
|
||||
@ -32,8 +32,8 @@ class Captain::Reservations::CreateService
|
||||
inbox = conversation&.inbox || find_inbox
|
||||
contact_inbox = conversation&.contact_inbox || find_or_create_contact_inbox(inbox)
|
||||
|
||||
raise ArgumentError, 'Inbox not found' unless inbox.present?
|
||||
raise ArgumentError, 'Contact not found' unless contact_inbox&.contact.present?
|
||||
raise ArgumentError, 'Inbox not found' if inbox.blank?
|
||||
raise ArgumentError, 'Contact not found' if contact_inbox&.contact.blank?
|
||||
|
||||
check_in_at = parse_time(params[:check_in_at])
|
||||
raise ArgumentError, 'Check-in time is required' unless check_in_at
|
||||
@ -149,13 +149,13 @@ class Captain::Reservations::CreateService
|
||||
end
|
||||
|
||||
def find_brand(brand_id)
|
||||
return unless brand_id.present?
|
||||
return if brand_id.blank?
|
||||
|
||||
Captain::Brand.find_by(id: brand_id, account_id: account.id)
|
||||
end
|
||||
|
||||
def find_unit(unit_id)
|
||||
return unless unit_id.present?
|
||||
return if unit_id.blank?
|
||||
|
||||
Captain::Unit.find_by(id: unit_id, account_id: account.id)
|
||||
end
|
||||
|
||||
@ -16,7 +16,7 @@ class Captain::Tools::BaseTool < RubyLLM::Tool
|
||||
RubyLLM.logger.debug "Tool #{name} returned: #{result.inspect}"
|
||||
|
||||
return result if result.is_a?(RubyLLM::Tool::Halt)
|
||||
return result unless fallback_message.present?
|
||||
return result if fallback_message.blank?
|
||||
return result unless errorish_result?(result)
|
||||
|
||||
fallback_message
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class CheckAvailabilityTool < BaseTool
|
||||
class Captain::Tools::CheckAvailabilityTool < BaseTool
|
||||
def name
|
||||
'check_availability'
|
||||
end
|
||||
@ -127,11 +125,10 @@ module Captain
|
||||
if available_options.present?
|
||||
msg = "Disponível! Para a suíte #{final_suite_category} em #{target_date&.strftime('%d/%m')}, tenho estas opções: #{available_options}. Pergunte qual duração o cliente prefere."
|
||||
Rails.logger.info "[CheckAvailabilityTool] MENU MODE: #{msg}"
|
||||
return msg
|
||||
else
|
||||
msg = "Não encontrei tarifas para a suíte #{final_suite_category} nesta data. Confirme o nome da suíte."
|
||||
return msg
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
pricing = pick_pricing_for_duration(pricing_scope, requested_duration)
|
||||
@ -143,7 +140,6 @@ module Captain
|
||||
)} (#{pricing.day_range})."
|
||||
persist_last_availability(final_suite_category, requested_duration, pricing, target_date)
|
||||
Rails.logger.info "[CheckAvailabilityTool] SUCCESS: #{msg}"
|
||||
return msg
|
||||
else
|
||||
available_options = pricing_scope.map do |p|
|
||||
"#{p.duration}: #{ActiveSupport::NumberHelper.number_to_currency(p.price.to_f, unit: 'R$ ', separator: ',', delimiter: '.')}"
|
||||
@ -155,9 +151,9 @@ module Captain
|
||||
msg = "Não encontrei tarifas cadastradas para a suíte #{final_suite_category} nesta data (#{target_date}). Por favor, confirme se o nome da suíte está correto."
|
||||
end
|
||||
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] FAILURE: #{msg}" }
|
||||
return msg
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] FAILURE: #{msg}" }
|
||||
end
|
||||
return msg
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[CheckAvailabilityTool] CRITICAL ERROR: #{e.message}\n#{e.backtrace.first(5).join("\n")}"
|
||||
raise e
|
||||
@ -389,7 +385,7 @@ module Captain
|
||||
return (start_idx..6).to_a + (0..end_idx).to_a
|
||||
end
|
||||
|
||||
normalized.split(',').map(&:strip).map { |name| mapping[name] }.compact
|
||||
normalized.split(',').map(&:strip).filter_map { |name| mapping[name] }
|
||||
end
|
||||
|
||||
def parse_date_from_text(text)
|
||||
@ -461,5 +457,3 @@ module Captain
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class CreateReservationIntentTool < BaseTool
|
||||
class Captain::Tools::CreateReservationIntentTool < BaseTool
|
||||
def name
|
||||
'create_reservation_intent'
|
||||
end
|
||||
@ -33,7 +31,7 @@ module Captain
|
||||
def execute(*args, **params)
|
||||
actual_params = resolve_params(args, params)
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] STARTING CreateReservationIntentTool with params: #{actual_params}"
|
||||
f.puts "[#{Time.zone.now}] STARTING CreateReservationIntentTool with params: #{actual_params}"
|
||||
end
|
||||
|
||||
suite_category = actual_params[:suite]
|
||||
@ -64,7 +62,7 @@ module Captain
|
||||
all_incoming = @conversation&.messages&.incoming&.order(created_at: :asc)&.last(10) || []
|
||||
last_reset_index = all_incoming.rindex { |m| m.content.to_s.downcase.match?(/\b(reiniciar|resetar|comecar de novo)\b/i) }
|
||||
|
||||
relevant_messages = last_reset_index ? all_incoming[(last_reset_index + 1)..-1] : all_incoming
|
||||
relevant_messages = last_reset_index ? all_incoming[(last_reset_index + 1)..] : all_incoming
|
||||
user_text_post_reset = relevant_messages.map(&:content).join(' ').downcase
|
||||
user_text_post_reset = ActiveSupport::Inflector.transliterate(user_text_post_reset).gsub(/[^\w\s]/, '')
|
||||
|
||||
@ -88,12 +86,12 @@ module Captain
|
||||
end
|
||||
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] INTENT BARRIER SCAN: Looking for #{valid_terms} in '#{user_text_post_reset}' -> Match: #{match_found}"
|
||||
f.puts "[#{Time.zone.now}] INTENT BARRIER SCAN: Looking for #{valid_terms} in '#{user_text_post_reset}' -> Match: #{match_found}"
|
||||
end
|
||||
|
||||
if suite_category.present? && !match_found
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] INTENT BLOCKED: Suite '#{suite_category}' (or aliases) not found after reset."
|
||||
f.puts "[#{Time.zone.now}] INTENT BLOCKED: Suite '#{suite_category}' (or aliases) not found after reset."
|
||||
end
|
||||
return "Atenção: O usuário ainda não escolheu a suíte '#{suite_category}' nesta nova conversa. Pergunte: 'Qual suíte você gostaria de reservar?'."
|
||||
end
|
||||
@ -101,40 +99,41 @@ module Captain
|
||||
# Global Price Lock: If the price the agent wants to charge
|
||||
# is different from the VERY LAST availability check, block it.
|
||||
# EXCEPTION: If explicit deposit_value is provided, we trust the agent knows what it's doing (e.g. promo/custom time)
|
||||
if price > 0 && last_availability.present? && !(deposit_override && deposit_override > 0) && price_mismatch?(price, last_availability[:price])
|
||||
if price.positive? && last_availability.present? && !(deposit_override && deposit_override.positive?) && price_mismatch?(price,
|
||||
last_availability[:price])
|
||||
msg = "ATENÇÃO: Preço (R$ #{format('%.2f',
|
||||
price)}) diverge da última cotação (R$ #{format('%.2f',
|
||||
last_availability[:price])} para #{last_availability[:suite]}). NÃO crie a reserva. Corrija o valor ou peça para o usuário confirmar."
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] PRICE BLOCK: Agent tried #{price} but last quote was #{last_availability[:price]}"
|
||||
f.puts "[#{Time.zone.now}] PRICE BLOCK: Agent tried #{price} but last quote was #{last_availability[:price]}"
|
||||
end
|
||||
return msg
|
||||
end
|
||||
|
||||
if suite_category.blank?
|
||||
msg = "SYSTEM INFO: Você esqueceu de informar a 'suite'. Pergunte ao cliente qual suíte e duração ele deseja."
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] RETURN: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] RETURN: #{msg}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
if price <= 0
|
||||
msg = 'SYSTEM INFO: Preço inválido. Use consultar_disponibilidade.'
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] RETURN: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] RETURN: #{msg}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
ensure_conversation_context!
|
||||
|
||||
unless @conversation && @conversation.inbox
|
||||
unless @conversation&.inbox
|
||||
msg = "Erro Crítico: Contexto de conversa não disponível (Conversation/Inbox nil). Params: #{actual_params}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] FAILURE: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] FAILURE: #{msg}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
unit = infer_unit
|
||||
unless unit
|
||||
msg = 'Erro: Unidade não encontrada para esta conversa. Verifique se o Inbox está conectado a uma Unidade.'
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] RETURN: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] RETURN: #{msg}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
@ -149,7 +148,7 @@ module Captain
|
||||
.first
|
||||
|
||||
# Determine Final Charge Amount
|
||||
deposit_amount = if deposit_override && deposit_override > 0
|
||||
deposit_amount = if deposit_override&.positive?
|
||||
deposit_override
|
||||
else
|
||||
price / 2.0
|
||||
@ -157,7 +156,7 @@ module Captain
|
||||
|
||||
if recent_draft && (recent_draft.total_amount.to_f - deposit_amount).abs < 0.1
|
||||
msg = "ATENÇÃO: A reserva JÁ FOI CRIADA anteriormente (ID: #{recent_draft.id}). NÃO crie novamente. Apenas CHAME A FERRAMENTA 'generate_pix' AGORA para finalizar."
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] IDEMPOTENCY HIT: Reuse draft #{recent_draft.id}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] IDEMPOTENCY HIT: Reuse draft #{recent_draft.id}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
@ -189,11 +188,11 @@ module Captain
|
||||
|
||||
msg = "Reserva iniciada com sucesso! O valor do sinal (50%) é: #{ActiveSupport::NumberHelper.number_to_currency(deposit_amount,
|
||||
unit: 'R$ ', separator: ',', delimiter: '.')}. INSTRUÇÃO: Como a reserva foi criada com sucesso, avise o cliente e CHAME IMEDIATAMENTE a ferramenta 'generate_pix' para entregar o código de pagamento."
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] SUCCESS: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] SUCCESS: #{msg}" }
|
||||
return msg
|
||||
rescue StandardError => e
|
||||
error_msg = "ERRO FATAL NA CRIACAO: #{e.message} | #{e.backtrace.first}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] EXCEPTION: #{error_msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] EXCEPTION: #{error_msg}" }
|
||||
return "Erro técnico ao criar reserva: #{e.message}"
|
||||
end
|
||||
end
|
||||
@ -307,7 +306,7 @@ module Captain
|
||||
end
|
||||
|
||||
def infer_from_history
|
||||
return {} unless @conversation.present?
|
||||
return {} if @conversation.blank?
|
||||
|
||||
suite_candidates = available_suite_categories
|
||||
|
||||
@ -360,5 +359,3 @@ module Captain
|
||||
match[1].tr('.', '').tr(',', '.').to_f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class Definitions
|
||||
class Captain::Tools::Definitions
|
||||
ALL = {
|
||||
'status_suites' => {
|
||||
type: :http,
|
||||
@ -54,5 +52,3 @@ module Captain
|
||||
}
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class GeneratePixTool < BaseTool
|
||||
class Captain::Tools::GeneratePixTool < BaseTool
|
||||
def name
|
||||
'generate_pix'
|
||||
end
|
||||
@ -24,7 +22,7 @@ module Captain
|
||||
def execute(*args, **params)
|
||||
actual_params = resolve_params(args, params)
|
||||
input_amount = actual_params[:amount].to_s.gsub(/[^\d,.]/, '').tr(',', '.')
|
||||
override_amount = input_amount.to_f if input_amount.present? && input_amount.to_f > 0
|
||||
override_amount = input_amount.to_f if input_amount.present? && input_amount.to_f.positive?
|
||||
|
||||
# ... (Validation Logic) ...
|
||||
# 1. Validate Contact Info
|
||||
@ -144,5 +142,3 @@ module Captain
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
module Captain
|
||||
module Tools
|
||||
module Parsers
|
||||
class StatusSuitesParser
|
||||
class Captain::Tools::Parsers::StatusSuitesParser
|
||||
def self.parse(response_body)
|
||||
# Assuming response_body is already a Hash or needs parsing
|
||||
data = response_body.is_a?(String) ? JSON.parse(response_body) : response_body rescue {}
|
||||
data = begin
|
||||
response_body.is_a?(String) ? JSON.parse(response_body) : response_body
|
||||
rescue StandardError
|
||||
{}
|
||||
end
|
||||
|
||||
# Example normalization logic based on prompt
|
||||
{
|
||||
@ -24,6 +25,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class ReactToMessageTool < BaseTool
|
||||
class Captain::Tools::ReactToMessageTool < BaseTool
|
||||
def name
|
||||
'react_to_message'
|
||||
end
|
||||
@ -29,7 +27,7 @@ module Captain
|
||||
def execute(*args, **params)
|
||||
actual_params = resolve_params(args, params)
|
||||
emoji = actual_params[:emoji]
|
||||
return error_response('Conversation not found') unless @conversation.present?
|
||||
return error_response('Conversation not found') if @conversation.blank?
|
||||
return error_response('Emoji is required') if emoji.blank?
|
||||
|
||||
# Get the last incoming message from the customer
|
||||
@ -76,5 +74,3 @@ module Captain
|
||||
{ success: false, error: message }.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class ReminderTool < BaseTool
|
||||
class Captain::Tools::ReminderTool < BaseTool
|
||||
def self.name
|
||||
'reminder'
|
||||
end
|
||||
@ -21,7 +19,7 @@ module Captain
|
||||
message = actual_params[:message]
|
||||
scheduled_at = actual_params[:scheduled_at]
|
||||
minutes_from_now = actual_params[:minutes_from_now]
|
||||
return error_response('Conversation not found') unless @conversation.present?
|
||||
return error_response('Conversation not found') if @conversation.blank?
|
||||
return error_response('Message is required') if message.blank?
|
||||
|
||||
schedule_time = parse_schedule_time(scheduled_at, minutes_from_now)
|
||||
@ -57,5 +55,3 @@ module Captain
|
||||
{ success: false, error: message }.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -45,7 +45,7 @@ class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseTool
|
||||
end
|
||||
|
||||
def low_confidence?(response)
|
||||
return false unless response&.respond_to?(:neighbor_distance)
|
||||
return false unless response.respond_to?(:neighbor_distance)
|
||||
|
||||
response.neighbor_distance.to_f > LOW_CONFIDENCE_DISTANCE
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class StatusSuitesTool < BaseTool
|
||||
class Captain::Tools::StatusSuitesTool < BaseTool
|
||||
def self.name
|
||||
'status_suites'
|
||||
end
|
||||
@ -57,5 +55,3 @@ module Captain
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class SuiteWatchdogTool < BaseTool
|
||||
class Captain::Tools::SuiteWatchdogTool < BaseTool
|
||||
def self.name
|
||||
'suite_watchdog'
|
||||
end
|
||||
@ -21,7 +19,7 @@ module Captain
|
||||
suite_identifier = actual_params[:suite_identifier]
|
||||
interval_minutes = actual_params[:interval_minutes] || 10
|
||||
message = actual_params[:message]
|
||||
return error_response('Conversation not found') unless @conversation.present?
|
||||
return error_response('Conversation not found') if @conversation.blank?
|
||||
return error_response('Suite identifier is required') if suite_identifier.blank?
|
||||
|
||||
reminder = Captain::Reminders::CreateService.new(
|
||||
@ -55,5 +53,3 @@ module Captain
|
||||
{ success: false, error: message }.to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
require 'net/http'
|
||||
require 'uri'
|
||||
|
||||
module Captain
|
||||
module Tools
|
||||
class ToolRunner
|
||||
class Captain::Tools::ToolRunner
|
||||
def self.run(assistant:, tool_key:, inbox:, conversation:, additional_data: {})
|
||||
new(assistant, tool_key, inbox, conversation, additional_data).run
|
||||
end
|
||||
@ -243,12 +241,10 @@ module Captain
|
||||
|
||||
# Specific payloads
|
||||
if @tool_key == 'maria_fotos'
|
||||
base.merge!(
|
||||
suite_category: extract_suite_category || 'Indefinida',
|
||||
message: 'User requested photos'
|
||||
)
|
||||
base[:suite_category] = extract_suite_category || 'Indefinida'
|
||||
base[:message] = 'User requested photos'
|
||||
elsif @tool_key == 'escalar_humano'
|
||||
base.merge!(reason: 'User requested human agent')
|
||||
base[:reason] = 'User requested human agent'
|
||||
end
|
||||
|
||||
base
|
||||
@ -306,11 +302,9 @@ module Captain
|
||||
end
|
||||
|
||||
def fallback_configured?
|
||||
return false unless @config&.fallback_message.present?
|
||||
return false if @config&.fallback_message.blank?
|
||||
return false if %w[faq_lookup react_to_message].include?(@tool_key)
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
module Captain
|
||||
module Tools
|
||||
class UpdateContactTool < BaseTool
|
||||
class Captain::Tools::UpdateContactTool < BaseTool
|
||||
def name
|
||||
'update_contact'
|
||||
end
|
||||
@ -29,7 +27,7 @@ module Captain
|
||||
def execute(*args, **params)
|
||||
actual_params = resolve_params(args, params)
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') do |f|
|
||||
f.puts "[#{Time.now}] STARTING UpdateContactTool with params: #{actual_params}"
|
||||
f.puts "[#{Time.zone.now}] STARTING UpdateContactTool with params: #{actual_params}"
|
||||
end
|
||||
|
||||
name = actual_params[:nome] || actual_params[:name]
|
||||
@ -39,9 +37,9 @@ module Captain
|
||||
|
||||
ensure_conversation_context!
|
||||
|
||||
unless @conversation && @conversation.contact
|
||||
unless @conversation&.contact
|
||||
msg = "Erro Crítico: Contexto de conversa ou contato não disponível. Params: #{actual_params}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] FAILURE: #{msg}" }
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] FAILURE: #{msg}" }
|
||||
return msg
|
||||
end
|
||||
|
||||
@ -53,13 +51,12 @@ module Captain
|
||||
if contact.save
|
||||
update_sticky_state(name: contact.name, cpf: contact.custom_attributes['cpf'])
|
||||
msg = "Dados atualizados com sucesso. Nome: #{contact.name}, CPF: #{contact.custom_attributes['cpf']}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] SUCCESS: #{msg}" }
|
||||
msg
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] SUCCESS: #{msg}" }
|
||||
else
|
||||
msg = "Erro ao salvar dados: #{contact.errors.full_messages.join(', ')}"
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.now}] FAILURE: #{msg}" }
|
||||
msg
|
||||
File.open(Rails.root.join('log/tool_debug.log'), 'a') { |f| f.puts "[#{Time.zone.now}] FAILURE: #{msg}" }
|
||||
end
|
||||
msg
|
||||
end
|
||||
|
||||
private
|
||||
@ -89,5 +86,3 @@ module Captain
|
||||
Rails.logger.warn "[UpdateContactTool] Failed to update sticky state: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
module Captain
|
||||
class WebhookSenderService
|
||||
class Captain::WebhookSenderService
|
||||
def initialize(reservation)
|
||||
@reservation = reservation
|
||||
@unit = reservation.unit
|
||||
end
|
||||
|
||||
def perform
|
||||
return unless @unit&.webhook_url.present?
|
||||
return if @unit&.webhook_url.blank?
|
||||
|
||||
payload = build_payload
|
||||
send_webhook(payload)
|
||||
@ -64,4 +63,3 @@ module Captain
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
module Captain
|
||||
class WhatsappNotificationService
|
||||
class Captain::WhatsappNotificationService
|
||||
def initialize(reservation)
|
||||
@reservation = reservation
|
||||
@unit = reservation.unit
|
||||
@ -95,4 +94,3 @@ module Captain
|
||||
'Estamos ansiosos para recebê-lo!'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -47,7 +47,7 @@ module Enterprise::MessageTemplates::HookExecutionService
|
||||
def send_typing_indicator
|
||||
# Access phone number safely via contact association
|
||||
phone = conversation.contact&.phone_number
|
||||
return unless phone.present?
|
||||
return if phone.blank?
|
||||
|
||||
# Assuming Wuzapi is the provider for Channel::Whatsapp in this context
|
||||
# We need to find the Wuzapi client instance or create one.
|
||||
|
||||
@ -14,7 +14,8 @@ class Llm::BaseAiService
|
||||
setup_temperature
|
||||
end
|
||||
|
||||
def chat(model: @model, temperature: @temperature, api_key: nil) # [INTENTIONAL] api_key reserved for per-request auth
|
||||
# [INTENTIONAL] api_key reserved for per-request auth
|
||||
def chat(model: @model, temperature: @temperature, api_key: nil)
|
||||
client = RubyLLM.chat(model: model)
|
||||
# client = client.with_api_key(api_key) if api_key.present?
|
||||
client.with_temperature(temperature)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class Llm::ModelTestService
|
||||
TEST_PROMPT = 'Reply with the word ok.'
|
||||
TEST_PROMPT = 'Reply with the word ok.'.freeze
|
||||
|
||||
def initialize(provider:, model:, api_key:)
|
||||
@provider = provider
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user