from odoo import models, fields, api, _ from odoo.exceptions import UserError import requests import logging _logger = logging.getLogger(__name__) AGENT_SERVICE_URL_PARAM = 'activeblue_ai.agent_service_url' WEBHOOK_SECRET_PARAM = 'activeblue_ai.webhook_secret' class AbAiBot(models.Model): _name = 'ab.ai.bot' _description = 'ActiveBlue AI Bot' _rec_name = 'display_name' display_name = fields.Char(string='Bot Name', default='ActiveBlue AI', required=True) active = fields.Boolean(default=True) agent_service_url = fields.Char( string='Agent Service URL', default='http://192.168.2.47:8001', required=True, ) webhook_secret = fields.Char(string='Webhook Secret') privacy_mode = fields.Selection( [('local', 'Local (Ollama only)'), ('hybrid', 'Hybrid'), ('cloud', 'Cloud (Claude)')], string='Privacy Mode', default='local', required=True, ) status = fields.Selection( [('online', 'Online'), ('offline', 'Offline'), ('error', 'Error')], string='Status', default='offline', readonly=True, ) last_ping = fields.Datetime(string='Last Ping', readonly=True) notes = fields.Text(string='Notes') @api.model def get_active_bot(self): bot = self.search([('active', '=', True)], limit=1) if not bot: raise UserError(_('No active AI bot configured. Please configure ActiveBlue AI.')) return bot def _get_service_url(self): self.ensure_one() return (self.agent_service_url or '').rstrip('/') def _build_headers(self): self.ensure_one() headers = {'Content-Type': 'application/json'} if self.webhook_secret: headers['X-ActiveBlue-Signature'] = self.webhook_secret return headers def action_ping(self): self.ensure_one() url = self._get_service_url() + '/health' try: resp = requests.get(url, timeout=5, headers=self._build_headers()) if resp.status_code == 200: self.write({'status': 'online', 'last_ping': fields.Datetime.now()}) return {'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {'message': _('AI service is online'), 'type': 'success'}} else: self.write({'status': 'error'}) return {'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {'message': _('AI service returned %s') % resp.status_code, 'type': 'warning'}} except Exception as exc: self.write({'status': 'offline'}) return {'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {'message': _('AI service unreachable: %s') % exc, 'type': 'danger'}} def dispatch_message(self, user_id, message, context=None, session_id=None): self.ensure_one() url = self._get_service_url() + '/dispatch' payload = { 'user_id': str(user_id), 'message': message, 'context': context or {}, } if session_id: payload['session_id'] = session_id try: resp = requests.post(url, json=payload, headers=self._build_headers(), timeout=120) resp.raise_for_status() return resp.json() except requests.exceptions.Timeout: raise UserError(_('AI service timed out. Please try again.')) except requests.exceptions.RequestException as exc: _logger.error('dispatch_message failed: %s', exc) raise UserError(_('Could not reach AI service: %s') % exc) @api.model def cron_ping_all(self): for bot in self.search([('active', '=', True)]): try: bot.action_ping() except Exception as exc: _logger.warning('Ping failed for bot %s: %s', bot.id, exc)