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:
@@ -11,10 +11,11 @@ CRM, sales, project management, eLearning, expenses, and HR.
|
|||||||
'website': 'https://activeblue.net',
|
'website': 'https://activeblue.net',
|
||||||
'category': 'Productivity',
|
'category': 'Productivity',
|
||||||
'license': 'LGPL-3',
|
'license': 'LGPL-3',
|
||||||
'depends': ['base', 'mail', 'web'],
|
'depends': ['base', 'mail', 'web', 'discuss'],
|
||||||
'data': [
|
'data': [
|
||||||
'security/res_groups.xml',
|
'security/res_groups.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
|
'data/res_partner_bot.xml',
|
||||||
'data/ir_cron.xml',
|
'data/ir_cron.xml',
|
||||||
'views/ab_ai_bot_views.xml',
|
'views/ab_ai_bot_views.xml',
|
||||||
'views/ab_ai_directive_views.xml',
|
'views/ab_ai_directive_views.xml',
|
||||||
|
|||||||
11
addons/activeblue_ai/data/res_partner_bot.xml
Normal file
11
addons/activeblue_ai/data/res_partner_bot.xml
Normal 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>
|
||||||
@@ -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
|
||||||
|
|||||||
71
addons/activeblue_ai/models/ab_ai_mail.py
Normal file
71
addons/activeblue_ai/models/ab_ai_mail.py
Normal 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
|
||||||
Reference in New Issue
Block a user