diff --git a/ai_oca_bridge_chatter/models/ai_bridge_execution.py b/ai_oca_bridge_chatter/models/ai_bridge_execution.py index adb74ca2..cd1e62f5 100644 --- a/ai_oca_bridge_chatter/models/ai_bridge_execution.py +++ b/ai_oca_bridge_chatter/models/ai_bridge_execution.py @@ -1,6 +1,8 @@ # Copyright 2025 Dixmit # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from markupsafe import Markup + from odoo import _, fields, models @@ -36,7 +38,18 @@ def _process_response_message(self, response): ) ) recipient._notify_typing(is_typing=False) - response["author_id"] = self.chatter_user_id.partner_id.id - response["message_type"] = "comment" + body = response.get("body") or "" + body_is_html = bool(response.pop("body_is_html", False)) + if not isinstance(body, Markup): + body = Markup(body) if body_is_html else Markup("

%s

") % body + response.update( + { + "author_id": self.chatter_user_id.partner_id.id, + "body": body, + "message_type": "comment", + # Discuss draws chat bubbles only for comments, not notes. + "subtype_xmlid": "mail.mt_comment", + } + ) return super()._process_response_message(response) diff --git a/ai_oca_bridge_chatter/tests/test_chatter.py b/ai_oca_bridge_chatter/tests/test_chatter.py index 67b4eda1..ad5f0149 100644 --- a/ai_oca_bridge_chatter/tests/test_chatter.py +++ b/ai_oca_bridge_chatter/tests/test_chatter.py @@ -82,12 +82,40 @@ def test_chat(self): body="Test message", ) mock_post.assert_called_once() - self.assertEqual( - 2, - self.env["mail.message"].search_count( - [("res_id", "=", self.chat.id), ("model", "=", "discuss.channel")] - ), + messages = self.env["mail.message"].search( + [("res_id", "=", self.chat.id), ("model", "=", "discuss.channel")], + order="id", + ) + self.assertEqual(2, len(messages)) + ai_message = messages[-1] + self.assertEqual(ai_message.author_id, self.ai_user.partner_id) + self.assertEqual(ai_message.message_type, "comment") + self.assertEqual(ai_message.subtype_id, self.env.ref("mail.mt_comment")) + self.assertIn("My message", ai_message.body) + + def test_chat_html_body_is_comment(self): + """HTML bridge replies must still be Discuss comments, not notes.""" + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, + json=lambda: { + "body": "

Hello! How can I help?

", + "body_is_html": True, + }, + ) + self.chat.with_user(self.user.id).message_post(body="ola") + ai_message = self.env["mail.message"].search( + [ + ("res_id", "=", self.chat.id), + ("model", "=", "discuss.channel"), + ("author_id", "=", self.ai_user.partner_id.id), + ], + limit=1, ) + self.assertTrue(ai_message) + self.assertEqual(ai_message.subtype_id, self.env.ref("mail.mt_comment")) + self.assertEqual(ai_message.message_type, "comment") + self.assertIn("Hello! How can I help?", ai_message.body) def test_channel_not_called(self): """No AI bridge should be called when the user is not callend in the channel"""