env.get() isn't an Odoo Environment method, so the existence guard silently returned without ever writing the bot's presence row. Use the 'in self.env' check instead.
128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
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):
|
|
any_online = False
|
|
for bot in self.search([('active', '=', True)]):
|
|
try:
|
|
bot.action_ping()
|
|
if bot.status == 'online':
|
|
any_online = True
|
|
except Exception as exc:
|
|
_logger.warning('Ping failed for bot %s: %s', bot.id, exc)
|
|
# Mirror agent-service health to the bot user's Discuss presence so it
|
|
# shows a green dot when the agent is reachable.
|
|
self._sync_bot_user_presence(online=any_online)
|
|
|
|
@api.model
|
|
def _sync_bot_user_presence(self, online):
|
|
bot_user = self.env['res.users'].sudo().search(
|
|
[('login', 'in', ('activeblue_ai_bot', 'activeblue_ai_bot@local'))], limit=1)
|
|
if not bot_user:
|
|
return
|
|
if 'bus.presence' not in self.env:
|
|
return
|
|
Presence = self.env['bus.presence']
|
|
status = 'online' if online else 'offline'
|
|
now = fields.Datetime.now()
|
|
rec = Presence.sudo().search([('user_id', '=', bot_user.id)], limit=1)
|
|
vals = {'status': status, 'last_poll': now, 'last_presence': now}
|
|
if rec:
|
|
rec.write(vals)
|
|
else:
|
|
vals['user_id'] = bot_user.id
|
|
Presence.sudo().create(vals)
|