UI nova dentro do Construtor (Hermes) — TabBar com Chat e Verificação. Verificação roda HermesBuilder::Validator (DB+runtime) e exibe resultado agrupado por categoria, com botão Refazer inline em FAIL/WARN reparáveis. Backend (porta dos checks DB do CLI bin/hermes-validate): - HermesBuilder::Validator com 22+ checks: engine, profile, port, secret, parent, unit, Brand, CaptainInbox sync (o bug que travou Juliana), pricing dry-run, Inter creds, typing/response_delay, registry MCP completo. - HermesBuilder::Repairer com 4 handlers automáticos: set_engine_hermes, sync_captain_inbox_unit, set_default_typing_delay, set_default_response_delay. - Endpoints novos: GET assistants, GET validate?slug=, POST repair. Frontend: - builder/Index.vue: wrapper com TabBar. - builder/BuilderChat.vue: extraído do Index original. - builder/BuilderVerification.vue: dropdown + Conferir agora + lista agrupada por categoria com badges + botão Refazer inline. i18n: keys em pt_BR e en sob CAPTAIN_HERMES_BUILDER.VERIFY.*. Filesystem/systemd checks ficam pro CLI hermes-validate (Rails container não enxerga /root/.hermes/profiles do host). Validado HTTP: GET /validate?slug=juliana_qnn1 → 28 PASS / 0 FAIL / 1 WARN. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
88 lines
3.3 KiB
Ruby
88 lines
3.3 KiB
Ruby
# Reparos automatizados pra checks FAIL/WARN identificados pelo Validator.
|
|
#
|
|
# Cada `repair_id` presente em Validator::Result mapeia pra um handler aqui.
|
|
# Reparos cobrem só estado em DB (Captain::Assistant, CaptainInbox, Inbox,
|
|
# config). Reparos de filesystem/systemd ficam pro CLI hermes-provision na
|
|
# VPS — UI mostra mensagem orientadora pro admin.
|
|
class HermesBuilder::Repairer
|
|
REPAIR_HANDLERS = %w[
|
|
set_engine_hermes
|
|
sync_captain_inbox_unit
|
|
set_default_typing_delay
|
|
set_default_response_delay
|
|
].freeze
|
|
|
|
def self.repair(slug:, repair_id:)
|
|
asst = ::Captain::Assistant.find_by(hermes_profile_name: slug, engine: 'hermes')
|
|
return failure("Assistant '#{slug}' não encontrado") if asst.nil?
|
|
return failure("Reparo '#{repair_id}' não suportado pela UI. Rode hermes-provision na VPS.") unless REPAIR_HANDLERS.include?(repair_id)
|
|
|
|
send("repair_#{repair_id}", asst)
|
|
rescue StandardError => e
|
|
Rails.logger.error("[HermesBuilder::Repairer] error: #{e.class}: #{e.message}")
|
|
failure("Erro: #{e.class}: #{e.message}")
|
|
end
|
|
|
|
class << self
|
|
private
|
|
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
def repair_set_engine_hermes(asst)
|
|
asst.update_columns(engine: 'hermes')
|
|
success("Engine setado pra 'hermes' no assistant #{asst.id}")
|
|
end
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
|
|
# Resincroniza CaptainInbox.captain_unit_id com Assistant.captain_unit_id.
|
|
# Esse foi o bug raiz que travou a Juliana — CaptainInbox apontava pra
|
|
# unit antiga (Dolce Amore) enquanto Assistant.captain_unit_id era a
|
|
# nova (Qnn01). Resolve_unit usa CaptainInbox como segundo nível e
|
|
# vazava unit errada quando o context['assistant_id'] não chegava.
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
def repair_sync_captain_inbox_unit(asst)
|
|
return failure('Assistant sem captain_unit_id setado — corrija primeiro pelo cadastro') if asst.captain_unit_id.blank?
|
|
|
|
ci = ::CaptainInbox.where(captain_assistant_id: asst.id).first
|
|
return failure('Sem CaptainInbox pra esse assistant — vincule no painel de inboxes primeiro') if ci.nil?
|
|
|
|
old = ci.captain_unit_id
|
|
ci.update_columns(captain_unit_id: asst.captain_unit_id)
|
|
|
|
::Captain::Unit.where(inbox_id: ci.inbox_id).where.not(id: asst.captain_unit_id).update_all(inbox_id: nil)
|
|
::Captain::Unit.where(id: asst.captain_unit_id).update_all(inbox_id: ci.inbox_id)
|
|
|
|
success("CaptainInbox.unit_id ressincronizado: #{old} → #{asst.captain_unit_id}")
|
|
end
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
|
|
def repair_set_default_typing_delay(asst)
|
|
ci = ::CaptainInbox.where(captain_assistant_id: asst.id).first
|
|
return failure('Sem inbox vinculada') if ci&.inbox.nil?
|
|
|
|
ci.inbox.update!(typing_delay: 5)
|
|
success("Inbox #{ci.inbox.id} typing_delay setado pra 5s")
|
|
end
|
|
|
|
def repair_set_default_response_delay(asst)
|
|
cfg = asst.config.to_h.merge(
|
|
'response_delay' => {
|
|
'mode' => 'typing_simulation',
|
|
'chars_per_second' => 25,
|
|
'min_seconds' => 1.5,
|
|
'max_seconds' => 6.0
|
|
}
|
|
)
|
|
asst.update!(config: cfg)
|
|
success('Humanização typing_simulation ativada (default: 25 cps, 1.5s..6s)')
|
|
end
|
|
|
|
def success(msg)
|
|
{ ok: true, message: msg }
|
|
end
|
|
|
|
def failure(msg)
|
|
{ ok: false, error: msg }
|
|
end
|
|
end
|
|
end
|