feat(addon): add ActiveBlue AI bot to Odoo Discuss

- Create res.partner for the AI bot (appears in DM contacts)
- Override mail.channel.message_post to intercept direct messages
  to the bot partner and forward them to the agent service
- Post the agent reply back into the Discuss channel as the bot
- Add discuss to depends; load res_partner_bot.xml data

Users can now open Discuss -> New Message -> search 'ActiveBlue AI'
to start a conversation with the agent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Garcia
2026-04-24 22:14:00 -04:00
parent 6cb09282c2
commit 992d2c2775
4 changed files with 85 additions and 2 deletions

View File

@@ -11,10 +11,11 @@ CRM, sales, project management, eLearning, expenses, and HR.
'website': 'https://activeblue.net',
'category': 'Productivity',
'license': 'LGPL-3',
'depends': ['base', 'mail', 'web'],
'depends': ['base', 'mail', 'web', 'discuss'],
'data': [
'security/res_groups.xml',
'security/ir.model.access.csv',
'data/res_partner_bot.xml',
'data/ir_cron.xml',
'views/ab_ai_bot_views.xml',
'views/ab_ai_directive_views.xml',

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="partner_activeblue_ai" model="res.partner">
<field name="name">ActiveBlue AI</field>
<field name="active">True</field>
<field name="partner_share">False</field>
<field name="comment">AI assistant — send a direct message to chat</field>
</record>
</data>
</odoo>

View File

@@ -1 +1 @@
from . import ab_ai_bot, ab_ai_directive, ab_ai_log, ab_ai_registry
from . import ab_ai_bot, ab_ai_directive, ab_ai_log, ab_ai_registry, ab_ai_mail

View File

@@ -0,0 +1,71 @@
from __future__ import annotations
import logging
import re
from odoo import models, api
_logger = logging.getLogger(__name__)
_HTML_TAG = re.compile(r'<[^>]+>')
def _strip_html(html: str) -> str:
return _HTML_TAG.sub(' ', html or '').strip()
class MailChannel(models.Model):
_inherit = 'mail.channel'
@api.model
def _ai_bot_partner(self):
return self.env.ref('activeblue_ai.partner_activeblue_ai', raise_if_not_found=False)
def message_post(self, *, body='', author_id=None, **kwargs):
result = super().message_post(body=body, author_id=author_id, **kwargs)
# Only intercept direct-message channels
if self.channel_type != 'chat':
return result
bot_partner = self._ai_bot_partner()
if not bot_partner:
return result
member_partners = self.channel_member_ids.partner_id
if bot_partner not in member_partners:
return result
# Don't react to the bot's own messages
if author_id == bot_partner.id:
return result
text = _strip_html(body)
if not text:
return result
# Identify the human sender
human_partner = member_partners.filtered(lambda p: p != bot_partner)[:1]
user = self.env['res.users'].search([('partner_id', '=', human_partner.id)], limit=1)
uid = user.id if user else self.env.uid
try:
bot = self.env['ab.ai.bot'].sudo().search([('active', '=', True)], limit=1)
if not bot:
return result
response = bot.dispatch_message(
user_id=uid,
message=text,
context={'channel_id': self.id, 'source': 'discuss'},
)
reply = (response or {}).get('reply') or (response or {}).get('message') or \
'I could not process your request right now.'
self.sudo().message_post(
body=reply,
author_id=bot_partner.id,
message_type='comment',
subtype_xmlid='mail.mt_comment',
)
except Exception as exc:
_logger.error('AI bot Discuss reply failed: %s', exc)
return result