From 810cfd8e03926026ae70ddeaca28254ab28c8e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Thu, 2 Jul 2026 10:56:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E8=B7=B3=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sun_badge_menu/__manifest__.py | 3 +- yuthon_ai_agent/__manifest__.py | 1 + yuthon_ai_agent/models/ai_agent.py | 12 ++++ yuthon_ai_agent/models/ai_rule.py | 64 +++++++++++++++++-- yuthon_ai_agent/static/src/js/ai_floating.js | 13 ++++ .../static/src/xml/ai_chat_template.xml | 5 ++ yuthon_ai_agent/views/ai_rule_views.xml | 21 ++++-- yuthon_base/static/src/scss/base.css | 6 +- yuthon_notice/models/yuthon_notice.py | 36 +++++------ 9 files changed, 127 insertions(+), 34 deletions(-) diff --git a/sun_badge_menu/__manifest__.py b/sun_badge_menu/__manifest__.py index 81ad773c6..24a8e8cad 100644 --- a/sun_badge_menu/__manifest__.py +++ b/sun_badge_menu/__manifest__.py @@ -17,7 +17,8 @@ "web.assets_backend": [ "sun_badge_menu/static/src/webclient/actions/client_actions.js", "sun_badge_menu/static/src/webclient/navbar/navbar.js", - "sun_badge_menu/static/src/webclient/navbar/navbar.xml" + "sun_badge_menu/static/src/webclient/navbar/navbar.xml", + "sun_badge_menu/static/src/scss/badge.scss" ], }, "installable": True, diff --git a/yuthon_ai_agent/__manifest__.py b/yuthon_ai_agent/__manifest__.py index b350cdf18..f5cfef0e4 100644 --- a/yuthon_ai_agent/__manifest__.py +++ b/yuthon_ai_agent/__manifest__.py @@ -37,6 +37,7 @@ 'web.assets_backend': [ 'yuthon_ai_agent/static/src/js/ai_chat_action.js', 'yuthon_ai_agent/static/src/js/ai_floating.js', + 'yuthon_ai_agent/static/src/js/ai_chat_open.js', 'yuthon_ai_agent/static/src/xml/ai_chat_template.xml', 'yuthon_ai_agent/static/src/css/ai_chat.css', 'yuthon_ai_agent/static/src/css/ai_floating.css', diff --git a/yuthon_ai_agent/models/ai_agent.py b/yuthon_ai_agent/models/ai_agent.py index a3af05f5e..f66a19748 100644 --- a/yuthon_ai_agent/models/ai_agent.py +++ b/yuthon_ai_agent/models/ai_agent.py @@ -526,6 +526,18 @@ class AiConversation(models.Model): Model.browse(ids).unlink() return {'success': True, 'deleted_ids': ids} + # 自定义模型方法调用(主动感知按钮触发) + # action_config 格式: {"type": "execute_tool", "tool": "方法名", "params": {"record_id": 123, "model": "yuthon.notice"}} + if model_name and args.get('record_id'): + record = self.env[model_name].browse(args['record_id']) + if not record.exists(): + return {'error': f'记录不存在:{model_name}, id={args["record_id"]}'} + method = getattr(record, name, None) + if not method or not callable(method): + return {'error': f'模型 {model_name} 没有可调用的方法:{name}'} + result = method() + return {'success': True, 'result': str(result) if result is not None else '执行成功'} + return {'error': f'未知工具:{name}'} except Exception as e: _logger.exception('Tool %s failed: %s', name, e) diff --git a/yuthon_ai_agent/models/ai_rule.py b/yuthon_ai_agent/models/ai_rule.py index 4e4174d02..a9ed8ad1c 100644 --- a/yuthon_ai_agent/models/ai_rule.py +++ b/yuthon_ai_agent/models/ai_rule.py @@ -223,16 +223,66 @@ class AiRule(models.Model): string='按钮文字', default='确认执行', ) - guide_button_action = fields.Char( - string='按钮方法配置', - help='按钮点击后执行的动作,JSON格式,如 {"type": "execute_tool", "tool": "notice_publish", "params": {"notice_id": "{record_id}"}}', - default='{}', - ) guide_method_name = fields.Char( - string='方法名称', - help='后台处理方法名,用于标识触发哪个逻辑', + string='后台方法名', + help='按钮点击后调用的后台方法名,如 notice_send_to_all。后台自动传入 record_id(当前记录ID)', ) guide_method_desc = fields.Char( string='方法说明', ) + def action_test_guide(self): + """测试主动感知:模拟触发,创建引导消息并打开AI对话""" + self.ensure_one() + if self.rule_type != 'active_guide': + return {'warning': {'title': '提示', 'message': '仅主动感知类型的规则可以测试'}} + + # 获取或创建用户对话 + conv = self.env['ai.conversation'].search([ + ('create_uid', '=', self.env.uid), + ], limit=1, order='write_date desc') + + if not conv: + provider = self.env['ai.provider'].search([('active', '=', True)], limit=1) + if not provider: + return {'warning': {'title': '错误', 'message': '请先配置 AI 服务商'}} + conv = self.env['ai.conversation'].create({ + 'name': 'AI 对话', + 'provider_id': provider.id, + }) + + # 组装测试消息(替换变量为测试值) + message = self.guide_message or f'【测试】主动感知已触发' + message = message.replace('{record_name}', '[测试记录]') + message = message.replace('{user_name}', self.env.user.name) + message = message.replace('{record_id}', '[测试]') + + # 组装 action config(按钮点击后的动作) + action_config = { + 'type': 'execute_tool', + 'tool': self.guide_method_name or '', + 'params': { + 'record_id': 0, + 'model': self.guide_model or '', + } + } + + # 创建 AI 消息(带 action 按钮) + self.env['ai.message'].create({ + 'conversation_id': conv.id, + 'role': 'assistant', + 'content': f'🧪 {message}', + 'action': json.dumps({ + 'type': 'guide_button', + 'label': self.guide_button_label or '确认执行', + 'action': action_config, + }, ensure_ascii=False), + }) + + # 返回 client action 打开浮动聊天框 + return { + 'type': 'ir.actions.client', + 'tag': 'yuthon_ai_chat_open', + 'params': {'conversation_id': conv.id}, + } + diff --git a/yuthon_ai_agent/static/src/js/ai_floating.js b/yuthon_ai_agent/static/src/js/ai_floating.js index 450f07114..0fdf8376d 100644 --- a/yuthon_ai_agent/static/src/js/ai_floating.js +++ b/yuthon_ai_agent/static/src/js/ai_floating.js @@ -637,3 +637,16 @@ patch(NavBar.prototype, { anchor.after(btn); }, }); + +// 监听来自 client action / 其他模块的"打开聊天"事件 +window.addEventListener("yuthon:ai_open_chat", async () => { + if (!isVisible) { + toggleChat(); + } + // 刷新消息列表以显示新创建的测试消息 + const msgs = await loadHistory(); + if (chatEl) { + renderMessages(msgs); + scrollToBottom(); + } +}); diff --git a/yuthon_ai_agent/static/src/xml/ai_chat_template.xml b/yuthon_ai_agent/static/src/xml/ai_chat_template.xml index 4b46dab35..d65019d72 100644 --- a/yuthon_ai_agent/static/src/xml/ai_chat_template.xml +++ b/yuthon_ai_agent/static/src/xml/ai_chat_template.xml @@ -115,4 +115,9 @@ + + + +
+ diff --git a/yuthon_ai_agent/views/ai_rule_views.xml b/yuthon_ai_agent/views/ai_rule_views.xml index f58db4cc5..d7dda08a7 100644 --- a/yuthon_ai_agent/views/ai_rule_views.xml +++ b/yuthon_ai_agent/views/ai_rule_views.xml @@ -69,14 +69,27 @@ +
+
- + placeholder="主动弹出时显示的文字,支持 {record_name} / {user_name}" /> + + + + + placeholder="方法功能说明,方便维护" /> +

+ 后台自动传入当前记录ID,无需手动配置参数 +

diff --git a/yuthon_base/static/src/scss/base.css b/yuthon_base/static/src/scss/base.css index 4127781a4..77ff08af7 100644 --- a/yuthon_base/static/src/scss/base.css +++ b/yuthon_base/static/src/scss/base.css @@ -1,4 +1,2 @@ -/* 隐藏控制面板右上角 Knowledge 搜索按钮(避免报错) */ -.o_knowledge_icon_search { - display: none !important; -} \ No newline at end of file +/* base.css — yuthon_base 通用样式 */ +/* 注意:菜单角标样式 (.menu-badge) 已移至 sun_badge_menu 模块 */ diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py index e3ee32d06..c5f9df9ea 100644 --- a/yuthon_notice/models/yuthon_notice.py +++ b/yuthon_notice/models/yuthon_notice.py @@ -278,35 +278,35 @@ class YuthonNotice(models.Model): ('guide_field_value', '=', 'True'), ('active', '=', True), ]) - + for rule in rules: # 生成提醒内容(替换变量) message = rule.guide_message or f'{record.name} 流程已结束' message = message.replace('{record_name}', record.name or '') message = message.replace('{user_name}', self.env.user.name or '') - message = message.replace('{record_id}', str(record.id)) - - # 解析按钮动作 - try: - action_config = json.loads(rule.guide_button_action or '{}') - except json.JSONDecodeError: - action_config = {} - - # 替换action_config中的变量 - action_config_str = json.dumps(action_config, ensure_ascii=False) - action_config_str = action_config_str.replace('{record_id}', str(record.id)) - action_config = json.loads(action_config_str) - + + # 自动组装按钮动作(后台自动注入 record_id) + action_config = {} + if rule.guide_method_name: + action_config = { + 'type': 'execute_tool', + 'tool': rule.guide_method_name, + 'params': { + 'record_id': record.id, + 'model': self._name, + } + } + # 查找或创建AI对话(用当前用户的默认对话) conversation = self.env['ai.conversation'].sudo().search([ ('create_uid', '=', self.env.user.id), ], limit=1, order='write_date desc') - + if not conversation: conversation = self.env['ai.conversation'].sudo().create({ 'name': 'AI 对话', }) - + # 创建AI消息(带action) self.env['ai.message'].sudo().create({ 'conversation_id': conversation.id, @@ -316,9 +316,9 @@ class YuthonNotice(models.Model): 'type': 'guide_button', 'label': rule.guide_button_label or '确认执行', 'action': action_config, - }, ensure_ascii=False), + }, ensure_ascii=False) if action_config else None, }) - + # TODO: 通过bus.bus推送到前端,让聊天框自动显示新消息 # self.env['bus.bus'].sudo()._sendone(self.env.user.partner_id, 'ai_guide_notification', { # 'conversation_id': conversation.id,