feat(captain): engine column + DB-driven Hermes routing + Express pricing
Marca cada Captain::Assistant com engine ('captain_interno' | 'hermes')
e move o roteamento Hermes do env var pro banco — admin troca engine
re-apontando a inbox no painel, sem deploy. Mantém fallback pras env
vars antigas (CAPTAIN_HERMES_INBOX_IDS etc) durante a migração gradual,
pra não quebrar Valentina antes da re-associação.
Frontend: badge "Hermes" (âmbar) ou "Interno" (cinza) ao lado de cada
assistant no dropdown switcher e no card da listagem, com chaves i18n
em en + pt_BR.
Tabela de preço (pricing_tables.rb): adiciona unit Express (id=5) e
estende a estrutura pra aceitar preço por dia da semana
(mon_wed/thu_sun) — necessário pro Express, retrocompatível com Dolce
Amore (preço único).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5f6aed05c9
commit
3182002bd9
@ -26,6 +26,10 @@ const props = defineProps({
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
engine: {
|
||||
type: String,
|
||||
default: 'captain_interno',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['action']);
|
||||
@ -76,11 +80,27 @@ const handleAction = ({ action, value }) => {
|
||||
<template>
|
||||
<CardLayout>
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<h6
|
||||
class="text-base font-normal text-n-slate-12 line-clamp-1 hover:underline transition-colors"
|
||||
>
|
||||
{{ name }}
|
||||
</h6>
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<h6
|
||||
class="text-base font-normal text-n-slate-12 line-clamp-1 hover:underline transition-colors"
|
||||
>
|
||||
{{ name }}
|
||||
</h6>
|
||||
<span
|
||||
v-if="engine === 'hermes'"
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-n-amber-3 text-n-amber-11 shrink-0"
|
||||
:title="t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_HERMES_TOOLTIP')"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_HERMES') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-n-slate-3 text-n-slate-11 shrink-0"
|
||||
:title="t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_INTERNO_TOOLTIP')"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_INTERNO') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
|
||||
@ -130,6 +130,20 @@ const openCreateAssistantDialog = () => {
|
||||
<span class="text-sm font-medium truncate text-n-slate-12">
|
||||
{{ assistant.name || '' }}
|
||||
</span>
|
||||
<span
|
||||
v-if="assistant.engine === 'hermes'"
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-n-amber-3 text-n-amber-11"
|
||||
:title="t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_HERMES_TOOLTIP')"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_HERMES') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-[10px] font-semibold uppercase tracking-wide px-1.5 py-0.5 rounded bg-n-slate-3 text-n-slate-11"
|
||||
:title="t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_INTERNO_TOOLTIP')"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.ENGINE_INTERNO') }}
|
||||
</span>
|
||||
<Avatar
|
||||
v-if="assistant"
|
||||
:name="assistant.name"
|
||||
|
||||
@ -385,7 +385,11 @@
|
||||
"ASSISTANTS": "Assistants",
|
||||
"SWITCH_ASSISTANT": "Switch between assistants",
|
||||
"NEW_ASSISTANT": "Create Assistant",
|
||||
"EMPTY_LIST": "No assistants found, please create one to get started"
|
||||
"EMPTY_LIST": "No assistants found, please create one to get started",
|
||||
"ENGINE_HERMES": "Hermes",
|
||||
"ENGINE_HERMES_TOOLTIP": "Assistant operated by the Hermes Agent (external LLM)",
|
||||
"ENGINE_INTERNO": "Internal",
|
||||
"ENGINE_INTERNO_TOOLTIP": "Assistant operated by the internal Captain orchestrator"
|
||||
},
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
|
||||
@ -366,7 +366,11 @@
|
||||
"ASSISTANTS": "Assistentes",
|
||||
"SWITCH_ASSISTANT": "Alternar entre assistentes",
|
||||
"NEW_ASSISTANT": "Criar Assistente",
|
||||
"EMPTY_LIST": "Nenhum assistente encontrado, crie um para começar"
|
||||
"EMPTY_LIST": "Nenhum assistente encontrado, crie um para começar",
|
||||
"ENGINE_HERMES": "Hermes",
|
||||
"ENGINE_HERMES_TOOLTIP": "Atendente operada pelo Hermes Agent (LLM externo)",
|
||||
"ENGINE_INTERNO": "Interno",
|
||||
"ENGINE_INTERNO_TOOLTIP": "Atendente operada pelo orquestrador interno do Captain"
|
||||
},
|
||||
"COPILOT": {
|
||||
"TITLE": "Copiloto",
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
class AddEngineToCaptainAssistants < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :captain_assistants, :engine, :string, default: 'captain_interno', null: false
|
||||
add_column :captain_assistants, :hermes_profile_name, :string
|
||||
add_column :captain_assistants, :hermes_webhook_base_url, :string
|
||||
add_index :captain_assistants, :engine
|
||||
end
|
||||
end
|
||||
@ -49,6 +49,7 @@ class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::Base
|
||||
|
||||
def assistant_params
|
||||
permitted = params.require(:assistant).permit(:name, :description, :orchestrator_prompt,
|
||||
:engine, :hermes_profile_name, :hermes_webhook_base_url,
|
||||
config: [
|
||||
:product_name, :feature_faq, :feature_memory, :feature_citation,
|
||||
:welcome_message, :handoff_message, :resolution_message,
|
||||
|
||||
@ -43,9 +43,17 @@ class Captain::Assistant < ApplicationRecord
|
||||
|
||||
store_accessor :config, :temperature, :feature_faq, :feature_memory, :product_name
|
||||
|
||||
ENGINES = %w[captain_interno hermes].freeze
|
||||
|
||||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
validates :account_id, presence: true
|
||||
validates :engine, inclusion: { in: ENGINES }
|
||||
validates :hermes_profile_name, presence: true, if: :hermes?
|
||||
validates :hermes_webhook_base_url, presence: true, if: :hermes?
|
||||
|
||||
scope :hermes, -> { where(engine: 'hermes') }
|
||||
scope :captain_interno, -> { where(engine: 'captain_interno') }
|
||||
|
||||
scope :ordered, -> { order(created_at: :desc) }
|
||||
|
||||
@ -55,6 +63,14 @@ class Captain::Assistant < ApplicationRecord
|
||||
name
|
||||
end
|
||||
|
||||
def hermes?
|
||||
engine == 'hermes'
|
||||
end
|
||||
|
||||
def captain_interno?
|
||||
engine == 'captain_interno'
|
||||
end
|
||||
|
||||
def available_agent_tools
|
||||
tools = self.class.built_in_agent_tools.dup
|
||||
|
||||
|
||||
@ -7,24 +7,32 @@
|
||||
# - Hermes invoca plugin captain-http-callback que POSTa de volta no Captain
|
||||
# - Captain cria mensagem outgoing e envia pro WhatsApp
|
||||
#
|
||||
# A ativação é por inbox via env var. As 9 outras inboxes do Captain seguem
|
||||
# usando o orquestrador interno (Daniela_Reservas, etc) sem mudança.
|
||||
# A ativação preferencial é DATA-DRIVEN: cada Captain::Assistant tem coluna
|
||||
# `engine` ('captain_interno' | 'hermes'). Inboxes apontam pra um assistant
|
||||
# via CaptainInbox; o engine do assistant determina o roteamento. Trocar de
|
||||
# engine = trocar a association no painel, sem deploy.
|
||||
#
|
||||
# Env vars:
|
||||
# CAPTAIN_HERMES_INBOX_IDS CSV de inbox.id (ex: "1,5"). Se vazio,
|
||||
# desativa em todas. Inboxes não listadas
|
||||
# continuam no fluxo Captain interno.
|
||||
# CAPTAIN_HERMES_WEBHOOK_BASE_URL Base URL do gateway Hermes
|
||||
# (default http://172.17.0.1:8644).
|
||||
# CAPTAIN_HERMES_CALLBACK_SECRET HMAC-SHA256 secret pra validar callback
|
||||
# do Hermes (X-Hermes-Callback-Signature).
|
||||
# Se vazio, validação é desabilitada (NÃO
|
||||
# recomendado em prod).
|
||||
# Por compatibilidade durante a migração (gradual), também respeitamos as
|
||||
# env vars antigas: se uma inbox está em CAPTAIN_HERMES_INBOX_IDS mas o
|
||||
# assistant ainda é 'captain_interno', tratamos como Hermes — assim Valentina
|
||||
# continua funcionando antes do admin re-apontar a inbox no painel.
|
||||
# Esse fallback deve ser removido depois que todos as inboxes migrarem.
|
||||
#
|
||||
# Env vars (apenas credenciais — config funcional vive no DB):
|
||||
# CAPTAIN_HERMES_CALLBACK_SECRET HMAC-SHA256 secret pra validar
|
||||
# callback do Hermes
|
||||
# (X-Hermes-Callback-Signature).
|
||||
# CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_<id>
|
||||
# Per-inbox secret retornado pelo
|
||||
# `hermes webhook subscribe`. Usado pra
|
||||
# assinar o POST OUTGOING. Sem ele, o
|
||||
# Hermes vai rejeitar o webhook.
|
||||
# `hermes webhook subscribe`. Usado
|
||||
# pra assinar o POST OUTGOING.
|
||||
#
|
||||
# Env vars LEGACY (em descontinuação — preferir DB):
|
||||
# CAPTAIN_HERMES_INBOX_IDS CSV de inbox.id. Usado só como
|
||||
# fallback até as inboxes terem
|
||||
# assistant com engine='hermes'.
|
||||
# CAPTAIN_HERMES_WEBHOOK_BASE_URL Base URL default. Idem.
|
||||
# CAPTAIN_HERMES_BASE_URL_INBOX_<id> Per-inbox base URL. Idem.
|
||||
module Captain::Hermes
|
||||
DEFAULT_BASE_URL = 'http://172.17.0.1:8644'.freeze
|
||||
|
||||
@ -34,23 +42,27 @@ module Captain::Hermes
|
||||
return false if inbox.blank?
|
||||
return false unless inbox.respond_to?(:id)
|
||||
|
||||
inbox_ids.include?(inbox.id)
|
||||
return true if assistant_for(inbox)&.hermes?
|
||||
|
||||
legacy_inbox_ids.include?(inbox.id)
|
||||
end
|
||||
|
||||
def inbox_ids
|
||||
@inbox_ids ||= ENV.fetch('CAPTAIN_HERMES_INBOX_IDS', '')
|
||||
.split(',')
|
||||
.map { |s| s.strip.to_i }
|
||||
.reject(&:zero?)
|
||||
.freeze
|
||||
def assistant_for(inbox)
|
||||
return nil if inbox.blank?
|
||||
return nil unless inbox.respond_to?(:captain_inbox)
|
||||
|
||||
inbox.captain_inbox&.captain_assistant
|
||||
end
|
||||
|
||||
def webhook_base_url
|
||||
@webhook_base_url ||= (ENV['CAPTAIN_HERMES_WEBHOOK_BASE_URL'].presence || DEFAULT_BASE_URL).chomp('/')
|
||||
def webhook_base_url(inbox = nil)
|
||||
assistant = assistant_for(inbox)
|
||||
return assistant.hermes_webhook_base_url.chomp('/') if assistant&.hermes? && assistant.hermes_webhook_base_url.present?
|
||||
|
||||
legacy_webhook_base_url(inbox)
|
||||
end
|
||||
|
||||
def webhook_url_for(inbox)
|
||||
"#{webhook_base_url}/webhooks/#{subscription_name_for(inbox)}"
|
||||
"#{webhook_base_url(inbox)}/webhooks/#{subscription_name_for(inbox)}"
|
||||
end
|
||||
|
||||
# Convenção de nome de subscription no Hermes: precisa bater com o que o
|
||||
@ -67,9 +79,25 @@ module Captain::Hermes
|
||||
ENV.fetch('CAPTAIN_HERMES_CALLBACK_SECRET', nil)
|
||||
end
|
||||
|
||||
# Reseta caches. Útil em specs ou após reload de config.
|
||||
def reset_cache!
|
||||
@inbox_ids = nil
|
||||
@webhook_base_url = nil
|
||||
@legacy_inbox_ids = nil
|
||||
end
|
||||
|
||||
# === Legacy (env var) fallbacks ===
|
||||
|
||||
def legacy_inbox_ids
|
||||
@legacy_inbox_ids ||= ENV.fetch('CAPTAIN_HERMES_INBOX_IDS', '')
|
||||
.split(',')
|
||||
.map { |s| s.strip.to_i }
|
||||
.reject(&:zero?)
|
||||
.freeze
|
||||
end
|
||||
|
||||
def legacy_webhook_base_url(inbox = nil)
|
||||
if inbox && (per_inbox = ENV.fetch("CAPTAIN_HERMES_BASE_URL_INBOX_#{inbox.id}", nil)).present?
|
||||
return per_inbox.chomp('/')
|
||||
end
|
||||
|
||||
(ENV['CAPTAIN_HERMES_WEBHOOK_BASE_URL'].presence || DEFAULT_BASE_URL).chomp('/')
|
||||
end
|
||||
end
|
||||
|
||||
@ -9,19 +9,25 @@
|
||||
# Estrutura: TABLES[captain_unit_id] = {
|
||||
# categories: {
|
||||
# '<categoria_key>' => {
|
||||
# prices: { '3h' => 85, 'pernoite_promo' => 110, ... },
|
||||
# aliases: ['apto', 'standard', 'apartamento standard', ...]
|
||||
# # Preço por período. Aceita 2 formatos:
|
||||
# # 1) Valor único (motel sem variação por dia da semana):
|
||||
# # prices: { '3h' => 85.0, 'pernoite_promo' => 110.0 }
|
||||
# # 2) Hash por bucket de dia da semana (hotel: qui-dom caro):
|
||||
# # prices: { '3h' => { 'mon_wed' => 50.0, 'thu_sun' => 65.0 } }
|
||||
# # Buckets aceitos: 'mon_wed' (seg-qua) e 'thu_sun' (qui-dom).
|
||||
# prices: { '3h' => 85, ... },
|
||||
# aliases: ['apto', 'standard', ...]
|
||||
# }
|
||||
# },
|
||||
# extra_person_fee: 45,
|
||||
# extra_person_rules: { '<categoria_key>' => starts_at_guest_n }
|
||||
# }
|
||||
#
|
||||
# Hoje só Dolce Amore (unit 4) está mapeado — Hermes só está ativo nele.
|
||||
# Conforme outras unidades migrarem pra Hermes, expandir aqui.
|
||||
# rubocop:disable Metrics/ModuleLength
|
||||
module Captain::Mcp::PricingTables
|
||||
PERIOD_KEYS = %w[3h pernoite_promo pernoite_integral diaria].freeze
|
||||
PERIOD_KEYS = %w[2h 3h 4h 5h pernoite_promo pernoite_integral diaria].freeze
|
||||
# mon_wed cobre wday 1,2,3 (seg-qua); thu_sun cobre wday 4,5,6,0 (qui-dom).
|
||||
DAY_BUCKETS = %w[mon_wed thu_sun].freeze
|
||||
DEFAULT_TZ = 'America/Sao_Paulo'.freeze
|
||||
|
||||
TABLES = {
|
||||
# Motel Dolce Amore — Ponta Negra, Natal/RN (captain_unit_id=4)
|
||||
@ -73,16 +79,74 @@ module Captain::Mcp::PricingTables
|
||||
aliases: ['chale master', 'chalé master', 'master 4 suites', 'chalé master 4 suítes', 'chale_master', '4 suites']
|
||||
}
|
||||
}
|
||||
},
|
||||
# Hotel 1001 Noites Express — Águas Lindas/GO (captain_unit_id=5)
|
||||
# Preço varia por dia da semana (mon_wed = seg-qua / thu_sun = qui-dom).
|
||||
# Diária e Família são flat (mesmo preço todos os dias).
|
||||
5 => {
|
||||
currency: 'BRL',
|
||||
extra_person_fee: 0.0,
|
||||
categories: {
|
||||
'standard' => {
|
||||
prices: {
|
||||
'2h' => { 'mon_wed' => 40.0, 'thu_sun' => 50.0 },
|
||||
'3h' => { 'mon_wed' => 50.0, 'thu_sun' => 65.0 },
|
||||
'4h' => { 'mon_wed' => 60.0, 'thu_sun' => 80.0 },
|
||||
'pernoite_promo' => { 'mon_wed' => 100.0, 'thu_sun' => 120.0 },
|
||||
'diaria' => 150.0
|
||||
},
|
||||
extra_person_starts_at: 3,
|
||||
aliases: ['standard', 'comum', 'básica', 'basica', 'apartamento standard']
|
||||
},
|
||||
'master' => {
|
||||
prices: {
|
||||
'2h' => { 'mon_wed' => 50.0, 'thu_sun' => 60.0 },
|
||||
'3h' => { 'mon_wed' => 60.0, 'thu_sun' => 75.0 },
|
||||
'4h' => { 'mon_wed' => 70.0 },
|
||||
'5h' => { 'thu_sun' => 85.0 },
|
||||
'pernoite_promo' => { 'mon_wed' => 120.0, 'thu_sun' => 140.0 },
|
||||
'diaria' => 160.0
|
||||
},
|
||||
extra_person_starts_at: 3,
|
||||
aliases: ['master', 'melhor', 'suite master', 'suíte master']
|
||||
},
|
||||
'singles' => {
|
||||
prices: {
|
||||
'pernoite_promo' => { 'mon_wed' => 80.0, 'thu_sun' => 110.0 },
|
||||
'diaria' => 130.0
|
||||
},
|
||||
extra_person_starts_at: 99,
|
||||
aliases: %w[singles single sozinho]
|
||||
},
|
||||
'familia' => {
|
||||
prices: {
|
||||
'pernoite_promo' => 160.0,
|
||||
'diaria' => 190.0
|
||||
},
|
||||
extra_person_starts_at: 99,
|
||||
aliases: %w[familia família familiar]
|
||||
},
|
||||
'singles_duplo' => {
|
||||
prices: {
|
||||
'pernoite_promo' => { 'mon_wed' => 180.0, 'thu_sun' => 220.0 },
|
||||
'diaria' => 250.0
|
||||
},
|
||||
extra_person_starts_at: 99,
|
||||
aliases: ['singles duplo', 'singles_duplo', 'casal', 'duplo']
|
||||
}
|
||||
}
|
||||
}
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
# Retorna {amount:, breakdown:} ou erro {error:} pra uma cobrança.
|
||||
# period: '3h' | 'pernoite_promo' | 'pernoite_integral' | 'diaria'
|
||||
# extra_guests: número TOTAL de hóspedes (não só os "extras" — a função
|
||||
# calcula extras baseado em extra_person_starts_at).
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def calculate(unit_id:, suite_category:, period:, total_guests: 2)
|
||||
# check_in_at: Time/String ISO8601. Determina o bucket de dia da semana
|
||||
# quando o preço varia (mon_wed/thu_sun). Default: agora.
|
||||
# total_guests: número TOTAL de hóspedes — a função calcula extras
|
||||
# baseado em extra_person_starts_at.
|
||||
# rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
||||
def calculate(unit_id:, suite_category:, period:, total_guests: 2, check_in_at: nil)
|
||||
table = TABLES[unit_id]
|
||||
return { error: "Unidade #{unit_id} não tem tabela de preços cadastrada." } if table.blank?
|
||||
|
||||
@ -92,8 +156,12 @@ module Captain::Mcp::PricingTables
|
||||
period_key = normalize_period(period)
|
||||
return { error: "Período '#{period}' inválido. Use: #{PERIOD_KEYS.join(', ')}." } if period_key.blank?
|
||||
|
||||
base = cat_data[:prices][period_key]
|
||||
return { error: "Preço de '#{period_key}' não definido para '#{cat_key}'." } if base.blank?
|
||||
raw = cat_data[:prices][period_key]
|
||||
return { error: "Preço de '#{period_key}' não definido para '#{cat_key}'." } if raw.blank?
|
||||
|
||||
day_bucket = resolve_day_bucket(check_in_at)
|
||||
base, used_bucket = resolve_price(raw, day_bucket, period_key, cat_key)
|
||||
return { error: base } if base.is_a?(String)
|
||||
|
||||
starts_at = cat_data[:extra_person_starts_at] || 3
|
||||
extra_guests = [total_guests.to_i - (starts_at - 1), 0].max
|
||||
@ -106,6 +174,7 @@ module Captain::Mcp::PricingTables
|
||||
unit_id: unit_id,
|
||||
suite_category: cat_key,
|
||||
period: period_key,
|
||||
day_bucket: used_bucket,
|
||||
base_price: base,
|
||||
total_guests: total_guests,
|
||||
extra_guests: extra_guests,
|
||||
@ -121,6 +190,35 @@ module Captain::Mcp::PricingTables
|
||||
|
||||
private
|
||||
|
||||
# Recebe Numeric (preço único) ou Hash{bucket=>preço}. Retorna [valor, bucket]
|
||||
# ou [erro_string, nil] se o bucket pedido não tiver preço cadastrado.
|
||||
def resolve_price(raw, day_bucket, period_key, cat_key)
|
||||
return [raw.to_f, nil] if raw.is_a?(Numeric)
|
||||
|
||||
return ["Estrutura de preço inválida pra '#{cat_key}/#{period_key}'.", nil] unless raw.is_a?(Hash)
|
||||
|
||||
price = raw[day_bucket] || raw[day_bucket.to_s]
|
||||
if price.blank?
|
||||
avail = raw.keys.map(&:to_s).join(', ')
|
||||
return ["'#{cat_key}/#{period_key}' não tem preço pro dia escolhido (#{day_bucket}). Disponível: #{avail}.", nil]
|
||||
end
|
||||
|
||||
[price.to_f, day_bucket]
|
||||
end
|
||||
|
||||
# mon_wed: wday 1,2,3 (seg, ter, qua)
|
||||
# thu_sun: wday 4,5,6,0 (qui, sex, sáb, dom)
|
||||
def resolve_day_bucket(check_in_at)
|
||||
time =
|
||||
case check_in_at
|
||||
when nil then Time.current.in_time_zone(DEFAULT_TZ)
|
||||
when Time, ActiveSupport::TimeWithZone, DateTime then check_in_at.in_time_zone(DEFAULT_TZ)
|
||||
else Time.zone.parse(check_in_at.to_s)&.in_time_zone(DEFAULT_TZ) || Time.current.in_time_zone(DEFAULT_TZ)
|
||||
end
|
||||
|
||||
[1, 2, 3].include?(time.wday) ? 'mon_wed' : 'thu_sun'
|
||||
end
|
||||
|
||||
def find_category(table, raw)
|
||||
needle = raw.to_s.downcase.strip.tr('_', ' ').squeeze(' ')
|
||||
return [nil, nil] if needle.blank?
|
||||
@ -145,7 +243,7 @@ module Captain::Mcp::PricingTables
|
||||
when 'diária' then 'diaria'
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/MethodLength,Metrics/AbcSize
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ModuleLength
|
||||
|
||||
@ -3,7 +3,10 @@ json.config resource.config
|
||||
json.created_at resource.created_at.to_i
|
||||
json.default_orchestrator_prompt Captain::PromptRenderer.read_template('assistant')
|
||||
json.description resource.description
|
||||
json.engine resource.engine
|
||||
json.guardrails resource.guardrails
|
||||
json.hermes_profile_name resource.hermes_profile_name
|
||||
json.hermes_webhook_base_url resource.hermes_webhook_base_url
|
||||
json.id resource.id
|
||||
json.name resource.name
|
||||
json.orchestrator_prompt resource.orchestrator_prompt
|
||||
|
||||
Loading…
Reference in New Issue
Block a user