From 0adb4b78b1b31cf92bfcaddc9adadd6d673d3ae5 Mon Sep 17 00:00:00 2001 From: Ernad Husremovic Date: Fri, 29 Aug 2025 15:43:05 +0200 Subject: [PATCH] Initial commit: OCA Ai packages (4 packages) --- README.md | 50 ++ odoo-bringout-oca-ai-ai_oca_bridge/README.md | 44 + .../ai_oca_bridge/README.rst | 205 +++++ .../ai_oca_bridge/__init__.py | 2 + .../ai_oca_bridge/__manifest__.py | 35 + .../ai_oca_bridge/controllers/__init__.py | 1 + .../ai_oca_bridge/controllers/ai.py | 48 ++ .../ai_oca_bridge/data/ir_module_category.xml | 7 + .../ai_oca_bridge/i18n/ai_oca_bridge.pot | 726 +++++++++++++++++ .../ai_oca_bridge/i18n/bs.po | 726 +++++++++++++++++ .../ai_oca_bridge/i18n/es.po | 754 ++++++++++++++++++ .../ai_oca_bridge/i18n/es_VE.po | 754 ++++++++++++++++++ .../ai_oca_bridge/i18n/it.po | 754 ++++++++++++++++++ .../ai_oca_bridge/i18n/pt_BR.po | 727 +++++++++++++++++ .../ai_oca_bridge/models/__init__.py | 5 + .../ai_oca_bridge/models/ai_bridge.py | 321 ++++++++ .../models/ai_bridge_execution.py | 218 +++++ .../ai_oca_bridge/models/ai_bridge_thread.py | 77 ++ .../ai_oca_bridge/models/ir_model.py | 28 + .../ai_oca_bridge/models/mail_thread.py | 68 ++ .../ai_oca_bridge/readme/CONFIGURE.md | 68 ++ .../ai_oca_bridge/readme/CONTEXT.md | 12 + .../ai_oca_bridge/readme/CONTRIBUTORS.md | 8 + .../ai_oca_bridge/readme/DESCRIPTION.md | 1 + .../ai_oca_bridge/readme/ROADMAP.md | 3 + .../ai_oca_bridge/readme/USAGE.md | 3 + .../security/ir.model.access.csv | 4 + .../ai_oca_bridge/security/security.xml | 19 + .../ai_oca_bridge/static/description/icon.png | Bin 0 -> 72370 bytes .../ai_oca_bridge/static/description/icon.svg | 49 ++ .../static/description/index.html | 560 +++++++++++++ .../src/components/chatter/chatter.esm.js | 32 + .../chatter_topbar/chatter_topbar.xml | 19 + .../chatter_topbar_ai.esm.js | 24 + .../chatter_topbar_ai/chatter_topbar_ai.xml | 27 + .../chatter_topbar_ai_item.esm.js | 46 ++ .../chatter_topbar_ai_item.xml | 22 + .../static/tests/helpers/mock_server.esm.js | 36 + .../tests/helpers/model_definition.esm.js | 29 + .../tests/web/test_ai_oca_bridge.esm.js | 98 +++ .../ai_oca_bridge/tests/__init__.py | 4 + .../ai_oca_bridge/tests/fake_models.py | 9 + .../ai_oca_bridge/tests/test_bridge.py | 367 +++++++++ .../ai_oca_bridge/tests/test_connection.py | 116 +++ .../ai_oca_bridge/tests/test_frontend.py | 12 + .../ai_oca_bridge/tests/test_mixin.py | 174 ++++ .../ai_oca_bridge/views/ai_bridge.xml | 149 ++++ .../views/ai_bridge_execution.xml | 88 ++ .../ai_oca_bridge/views/menu.xml | 17 + .../doc/ARCHITECTURE.md | 32 + .../doc/CONFIGURATION.md | 3 + .../doc/CONTROLLERS.md | 17 + .../doc/DEPENDENCIES.md | 5 + odoo-bringout-oca-ai-ai_oca_bridge/doc/FAQ.md | 4 + .../doc/INSTALL.md | 7 + .../doc/MODELS.md | 16 + .../doc/OVERVIEW.md | 6 + .../doc/REPORTS.md | 3 + .../doc/SECURITY.md | 41 + .../doc/TROUBLESHOOTING.md | 5 + .../doc/USAGE.md | 7 + .../doc/WIZARDS.md | 3 + .../pyproject.toml | 42 + .../README.md | 44 + .../ai_oca_bridge_chatter/README.rst | 103 +++ .../ai_oca_bridge_chatter/__init__.py | 1 + .../ai_oca_bridge_chatter/__manifest__.py | 19 + .../i18n/ai_oca_bridge_chatter.pot | 86 ++ .../ai_oca_bridge_chatter/i18n/bs.po | 86 ++ .../ai_oca_bridge_chatter/i18n/es.po | 91 +++ .../ai_oca_bridge_chatter/i18n/es_VE.po | 91 +++ .../ai_oca_bridge_chatter/i18n/it.po | 91 +++ .../ai_oca_bridge_chatter/i18n/pt_BR.po | 87 ++ .../ai_oca_bridge_chatter/models/__init__.py | 5 + .../ai_oca_bridge_chatter/models/ai_bridge.py | 32 + .../models/ai_bridge_execution.py | 41 + .../models/mail_channel.py | 50 ++ .../models/res_partner.py | 21 + .../ai_oca_bridge_chatter/models/res_users.py | 25 + .../ai_oca_bridge_chatter/readme/CONFIGURE.md | 9 + .../readme/CONTRIBUTORS.md | 5 + .../readme/DESCRIPTION.md | 3 + .../static/description/Chat.json | 185 +++++ .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 448 +++++++++++ .../ai_oca_bridge_chatter/tests/__init__.py | 1 + .../tests/test_chatter.py | 183 +++++ .../ai_oca_bridge_chatter/views/ai_bridge.xml | 15 + .../ai_oca_bridge_chatter/views/res_users.xml | 18 + .../doc/ARCHITECTURE.md | 32 + .../doc/CONFIGURATION.md | 3 + .../doc/CONTROLLERS.md | 3 + .../doc/DEPENDENCIES.md | 5 + .../doc/FAQ.md | 4 + .../doc/INSTALL.md | 7 + .../doc/MODELS.md | 16 + .../doc/OVERVIEW.md | 6 + .../doc/REPORTS.md | 3 + .../doc/SECURITY.md | 8 + .../doc/TROUBLESHOOTING.md | 5 + .../doc/USAGE.md | 7 + .../doc/WIZARDS.md | 3 + .../pyproject.toml | 42 + .../README.md | 45 ++ .../ai_oca_bridge_document_page/README.rst | 137 ++++ .../ai_oca_bridge_document_page/__init__.py | 1 + .../__manifest__.py | 19 + .../demo/ai_bridge_demo.xml | 58 ++ .../i18n/ai_oca_bridge_document_page.pot | 49 ++ .../ai_oca_bridge_document_page/i18n/bs.po | 49 ++ .../ai_oca_bridge_document_page/i18n/es.po | 53 ++ .../ai_oca_bridge_document_page/i18n/es_VE.po | 53 ++ .../ai_oca_bridge_document_page/i18n/it.po | 52 ++ .../ai_oca_bridge_document_page/i18n/pt_BR.po | 50 ++ .../models/__init__.py | 1 + .../models/document_page.py | 6 + .../readme/CONFIGURE.md | 26 + .../readme/CONTRIBUTORS.md | 3 + .../readme/DESCRIPTION.md | 3 + .../readme/USAGE.md | 3 + .../RagCapabilitiesWithOdooKnowledge.json | 317 ++++++++ .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 494 ++++++++++++ .../tests/__init__.py | 4 + .../tests/test_document_page_ai_bridge.py | 230 ++++++ .../doc/ARCHITECTURE.md | 32 + .../doc/CONFIGURATION.md | 3 + .../doc/CONTROLLERS.md | 3 + .../doc/DEPENDENCIES.md | 6 + .../doc/FAQ.md | 4 + .../doc/INSTALL.md | 7 + .../doc/MODELS.md | 12 + .../doc/OVERVIEW.md | 6 + .../doc/REPORTS.md | 3 + .../doc/SECURITY.md | 8 + .../doc/TROUBLESHOOTING.md | 5 + .../doc/USAGE.md | 7 + .../doc/WIZARDS.md | 3 + .../pyproject.toml | 43 + .../README.md | 45 ++ .../ai_oca_bridge_helpdesk_mgmt/README.rst | 83 ++ .../ai_oca_bridge_helpdesk_mgmt/__init__.py | 1 + .../__manifest__.py | 14 + .../i18n/ai_oca_bridge_helpdesk_mgmt.pot | 19 + .../ai_oca_bridge_helpdesk_mgmt/i18n/bs.po | 19 + .../ai_oca_bridge_helpdesk_mgmt/i18n/es.po | 22 + .../ai_oca_bridge_helpdesk_mgmt/i18n/es_VE.po | 22 + .../ai_oca_bridge_helpdesk_mgmt/i18n/it.po | 22 + .../ai_oca_bridge_helpdesk_mgmt/i18n/pt_BR.po | 20 + .../models/__init__.py | 1 + .../models/helpdesk_ticket.py | 10 + .../readme/CONTRIBUTORS.md | 2 + .../readme/DESCRIPTION.md | 1 + .../static/description/icon.png | Bin 0 -> 93215 bytes .../static/description/icon.svg | 113 +++ .../static/description/index.html | 433 ++++++++++ .../doc/ARCHITECTURE.md | 32 + .../doc/CONFIGURATION.md | 3 + .../doc/CONTROLLERS.md | 3 + .../doc/DEPENDENCIES.md | 6 + .../doc/FAQ.md | 4 + .../doc/INSTALL.md | 7 + .../doc/MODELS.md | 12 + .../doc/OVERVIEW.md | 6 + .../doc/REPORTS.md | 3 + .../doc/SECURITY.md | 8 + .../doc/TROUBLESHOOTING.md | 5 + .../doc/USAGE.md | 7 + .../doc/WIZARDS.md | 3 + .../pyproject.toml | 43 + 170 files changed, 12385 insertions(+) create mode 100644 README.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/README.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/README.rst create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__manifest__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/ai.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/data/ir_module_category.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/ai_oca_bridge.pot create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/bs.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es_VE.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/it.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/pt_BR.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_execution.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_thread.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ir_model.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/mail_thread.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONFIGURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTEXT.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTRIBUTORS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/DESCRIPTION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/ROADMAP.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/ir.model.access.csv create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/security.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.png create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.svg create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/index.html create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter/chatter.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar/chatter_topbar.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/mock_server.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/model_definition.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/web/test_ai_oca_bridge.esm.js create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/fake_models.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_bridge.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_connection.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_frontend.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_mixin.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge_execution.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/menu.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/ARCHITECTURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/CONFIGURATION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/CONTROLLERS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/DEPENDENCIES.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/FAQ.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/INSTALL.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/MODELS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/OVERVIEW.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/REPORTS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/SECURITY.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/TROUBLESHOOTING.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/doc/WIZARDS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge/pyproject.toml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/README.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/README.rst create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__manifest__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/ai_oca_bridge_chatter.pot create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/bs.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es_VE.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/it.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/pt_BR.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge_execution.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/mail_channel.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_partner.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_users.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONFIGURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONTRIBUTORS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/DESCRIPTION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/Chat.json create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/icon.png create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/index.html create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/test_chatter.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/ai_bridge.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/res_users.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/ARCHITECTURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONFIGURATION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONTROLLERS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/DEPENDENCIES.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/FAQ.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/INSTALL.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/MODELS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/OVERVIEW.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/REPORTS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/SECURITY.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/TROUBLESHOOTING.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/WIZARDS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_chatter/pyproject.toml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/README.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/README.rst create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__manifest__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/demo/ai_bridge_demo.xml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/ai_oca_bridge_document_page.pot create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/bs.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es_VE.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/it.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/pt_BR.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/document_page.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONFIGURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONTRIBUTORS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/DESCRIPTION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/RagCapabilitiesWithOdooKnowledge.json create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/icon.png create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/index.html create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/test_document_page_ai_bridge.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/ARCHITECTURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONFIGURATION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONTROLLERS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/DEPENDENCIES.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/FAQ.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/INSTALL.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/MODELS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/OVERVIEW.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/REPORTS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/SECURITY.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/TROUBLESHOOTING.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/WIZARDS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_document_page/pyproject.toml create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/README.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/README.rst create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__manifest__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/ai_oca_bridge_helpdesk_mgmt.pot create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/bs.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es_VE.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/it.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/pt_BR.po create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/__init__.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/helpdesk_ticket.py create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/CONTRIBUTORS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/DESCRIPTION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/icon.png create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/icon.svg create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/index.html create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/ARCHITECTURE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONFIGURATION.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONTROLLERS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/DEPENDENCIES.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/FAQ.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/INSTALL.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/MODELS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/OVERVIEW.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/REPORTS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/SECURITY.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/TROUBLESHOOTING.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/USAGE.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/WIZARDS.md create mode 100644 odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/pyproject.toml diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1e957f --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# OCA Ai + +This repository contains **4** OCA packages for ai. + +## Packages Included (4 packages) + +- **odoo-bringout-oca-ai-ai_oca_bridge** - From ai: ai_oca_bridge +- **odoo-bringout-oca-ai-ai_oca_bridge_chatter** - From ai: ai_oca_bridge_chatter +- **odoo-bringout-oca-ai-ai_oca_bridge_document_page** - From ai: ai_oca_bridge_document_page +- **odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt** - From ai: ai_oca_bridge_helpdesk_mgmt + + +## Installation + +Install any package from this category: + +```bash +# Install from local directory +pip install packages/oca-ai/PACKAGE_NAME/ + +# Install in development mode +pip install -e packages/oca-ai/PACKAGE_NAME/ + +# Using uv (recommended for speed) +uv add packages/oca-ai/PACKAGE_NAME/ +``` + +## Repository Structure + +Each package in this repository follows the standard Odoo addon structure: + +``` +oca-ai/ +├── odoo-bringout-oca-PROJECT-ADDON/ +│ ├── ADDON_NAME/ # Complete addon code +│ │ ├── __init__.py +│ │ ├── __manifest__.py +│ │ └── ... (models, views, etc.) +│ ├── pyproject.toml # Python package configuration +│ └── README.md # Package documentation +└── ... +``` + +## Contributing + +These packages are maintained as part of the [OCA (Odoo Community Association)](https://github.com/OCA) ecosystem. + +## License + +Each package maintains its original license as specified in the OCA repositories. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/README.md b/odoo-bringout-oca-ai-ai_oca_bridge/README.md new file mode 100644 index 0000000..a5269b4 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/README.md @@ -0,0 +1,44 @@ +# AI OCA Bridge + +Odoo addon: ai_oca_bridge + +## Installation + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge +``` + +## Dependencies + +This addon depends on: +- mail + +## Manifest Information + +- **Name**: AI OCA Bridge +- **Version**: 16.0.2.0.1 +- **Category**: AI +- **License**: AGPL-3 +- **Installable**: False + +## Source + +Based on [OCA/ai](https://github.com/OCA/ai) branch 16.0, addon `ai_oca_bridge`. + +## License + +This package maintains the original AGPL-3 license from the upstream Odoo project. + +## Documentation + +- Overview: doc/OVERVIEW.md +- Architecture: doc/ARCHITECTURE.md +- Models: doc/MODELS.md +- Controllers: doc/CONTROLLERS.md +- Wizards: doc/WIZARDS.md +- Install: doc/INSTALL.md +- Usage: doc/USAGE.md +- Configuration: doc/CONFIGURATION.md +- Dependencies: doc/DEPENDENCIES.md +- Troubleshooting: doc/TROUBLESHOOTING.md +- FAQ: doc/FAQ.md diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/README.rst b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/README.rst new file mode 100644 index 0000000..f167ca2 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/README.rst @@ -0,0 +1,205 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============= +AI OCA Bridge +============= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:95324eee7973f3fc8e883c34e3e72df6337726fe9eac34f04e68145eeaed22da + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github + :target: https://github.com/OCA/ai/tree/16.0/ai_oca_bridge + :alt: OCA/ai +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_oca_bridge + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is used to create a bridge between Odoo and other AI systems +like n8n. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +Right now, there are 2 different approaches for AI integration with +Odoo: + +1. Make everything inside Odoo. +2. Make it using other tools and integrate Odoo with these tools. + +IMO, it would be better to make use of option 2 for different reasons: + +- Odoo server is intended as a transactional system. AI systems requires + other kind of characteristics +- Everything changes too fast. I am not confident that Odoo can keep the + pace in this topic +- There are OSS tools that fills the gap perfectly and are created just + for this topic. + +Anyway, OCA is open to everyone and we don't intend to force an +opinionated way of doing. For this reason, we have this module, that can +be used as Bridge with AI systems. + +Configuration +============= + +As an administrator access ``AI Bridge\AI Bridge``. + +Create a new bridge. Define the name, model, url and configuration. + +In order to improve the view of the AI configuration, use groups and +domain to set better filters. + +Payload Configuration +--------------------- + +On the external system, you will receive a POST payload. The data +included will be the following: + +General +~~~~~~~ + +- \_odoo: Standard data to identify the Odoo Database +- \_model: Model of the related object +- \_id: Id of the related object +- \_response_url: Url to call with the response in case of async calls + +Record Payload +~~~~~~~~~~~~~~ + +Adds a new item called record with all the fields. + +Record Payload (v0) +~~~~~~~~~~~~~~~~~~~ + +Adds all the fields directly on the payload. It will be removed on 17.0. + +Asynchronous and synchronous calls +---------------------------------- + +The new system allows asynchronous and synchronous calls. Asynchronous +calls makes sense when the task to be processed don't need to be +immediate. For example, reviewing an invoice and leave a comment with +the result. The same would happen with a chat message. We expect that +the system will leave time to the AI to answer and Odoo's user can do +other things. + +Meanwhile, Synchronous calls will froze odoo system and wait for an +answer. This makes sense when we expect some feedback from odoo user. It +makes sense, when we open an action for example. + +In the synchronous call, the result is processed when the AI system +answers on the webhook. On the other hand, it will be processed +automatically on the synchronous call. + +Result processing +----------------- + +With the answers of the system we expect to do something about it. We +have the following options: + +No processing +~~~~~~~~~~~~~ + +In this case, the result will do nothing + +Post a Message +~~~~~~~~~~~~~~ + +We will post a message on the original thread of the system. The thread +is computed by a function, so it can be overriden in future modules. It +expects the keyword arguments of the ``message_post`` function. + +Action +~~~~~~ + +It expects to launch an action on the user interface. It only makes +sense on synchronous calls. + +It expects an action item with the following parameters: + +- action: xmlid of the action +- context: Context to pass to the action (not required) +- res_id: Id of the resource (not required) + +Usage +===== + +Use the bolt widget in the chatter to execute the different AI options. + +The options will be filtered according to the configuration. + +Known issues / Roadmap +====================== + +- Define examples to use and import +- Allow child fields. Right now, only first level fields are accepted. +- Information popover is not working properly when there is large data. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Dixmit + +Contributors +------------ + +- `Dixmit `__ + + - Enric Tobella + +- `Sygel Technology `__ + + - Valentín Vinagre + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/ai `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__init__.py new file mode 100644 index 0000000..91c5580 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__manifest__.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__manifest__.py new file mode 100644 index 0000000..cf8a635 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/__manifest__.py @@ -0,0 +1,35 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "AI OCA Bridge", + "summary": """Makes a basic configuration to be used as bridge with external AI systems""", + "version": "16.0.2.0.1", + "license": "AGPL-3", + "author": "Dixmit,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/ai", + "category": "AI", + "development_status": "Beta", + "depends": ["mail"], + "data": [ + "data/ir_module_category.xml", + "security/ir.model.access.csv", + "security/security.xml", + "views/menu.xml", + "views/ai_bridge_execution.xml", + "views/ai_bridge.xml", + ], + "assets": { + "web.assets_backend": [ + "ai_oca_bridge/static/src/**/*.xml", + "ai_oca_bridge/static/src/**/*.esm.js", + ], + "web.qunit_suite_tests": [ + "ai_oca_bridge/static/tests/web/**/*.esm.js", + ], + "web.tests_assets": [ + "ai_oca_bridge/static/tests/helpers/**/*.esm.js", + ], + }, + "application": True, +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/__init__.py new file mode 100644 index 0000000..5a0e9a5 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/__init__.py @@ -0,0 +1 @@ +from . import ai diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/ai.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/ai.py new file mode 100644 index 0000000..06668ec --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/controllers/ai.py @@ -0,0 +1,48 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json + +from odoo import fields, http +from odoo.http import request +from odoo.tools import consteq +from odoo.tools.translate import _ + + +class AIController(http.Controller): + @http.route( + [ + "/ai/response//", + ], + type="http", + auth="public", + cors="*", + csrf=False, + ) + def ai_process_response(self, execution_id, token): + execution = request.env["ai.bridge.execution"].sudo().browse(execution_id) + if not execution.exists(): + return request.make_response(_("Execution not found."), status=404) + if not consteq(execution._generate_token(), token): + return request.make_response( + _("Token is not allowed for this execution."), status=404 + ) + if ( + not execution.expiration_date + or execution.expiration_date < fields.Datetime.now() + ): + return request.make_response(_("Execution is expired."), status=404) + return request.make_response( + json.dumps( + execution._process_response( + json.loads( + request.httprequest.get_data().decode( + request.httprequest.charset + ) + ) + ) + ), + headers=[ + ("Content-Type", "application/json"), + ], + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/data/ir_module_category.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/data/ir_module_category.xml new file mode 100644 index 0000000..3917769 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/data/ir_module_category.xml @@ -0,0 +1,7 @@ + + + + AI + 99 + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/ai_oca_bridge.pot b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/ai_oca_bridge.pot new file mode 100644 index 0000000..ddbf4aa --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/ai_oca_bridge.pot @@ -0,0 +1,726 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/bs.po b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/bs.po new file mode 100644 index 0000000..f77ff39 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/bs.po @@ -0,0 +1,726 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "%s uspešno izvršeno." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "%s neuspešno." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "%s nije aktivno." + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "AI" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "AI most" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "AI most izvršen" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "AI most izvršavanje" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "AI most neuspešan" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "AI most neaktivan" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "AI most mixin" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "AI izvršavanje" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "AI nit kreiranje" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "AI nit ukloni" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "AI nit piši" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "Akcija" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "Potrebna akcija" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "Aktivan" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "Aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Dekoracija iznimke aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "Status aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "Ikona tipa aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "Ai most" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Konfiguracija AI mosta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "Ai most informacije" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ai izvršavanje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "Ai upotreba" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "Arhivirano" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "Asinhroni timeout" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "Asinhrono" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "Broj priloga" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "Auth lozinka" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "Auth token" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "Auth korisničko ime" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "Tip autentifikacije" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "Osnovna autentifikacija" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "Preduzeće" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "Kreirao" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "Kreirano" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "Definiše tip rezultata očekivanog od AI sistema." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "Opis" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "Prikazani naziv" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "Gotovo" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "U pripremi" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "Nit e-pošte" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "Greška" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "Izvršavanje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "Broj izvršavanja" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "Detalji izvršavanja" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "Izvršavanje je isteklo." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "Izvršavanje nije pronađeno." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "Datum isteka" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "Datum isteka za token asinhrone operacije." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "Polje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "Polja za uključivanje u AI most." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "Filter" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "Pratioci" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "Pratioci (Partneri)" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Font awesome ikona npr. fa-tasks" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "Grupa" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "Ima poruku" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "ID" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "Ikona" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Ikona za prikaz iznimki." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Ako je zakačeno, nove poruke će zahtjevati vašu pažnju" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Ako je označeno neke poruke mogu imati grešku u dostavi." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "Trenutno" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "Je Ai most nit" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "Pratilac" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "Zadnje mijenjano" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "Zadnji ažurirao" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "Zadnje ažurirano" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "Glavna zakačka" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "Greška pri isporuci poruke" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "Poruke" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "Model" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "Naziv modela" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "Model obavezan" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "Modeli" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Rok za moju aktivnost" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "Naziv:" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Krajnji rok za sljedeću aktivnost" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "Pregled sljedeće aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "Tip sljedeće aktivnosti" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "Nema korisnog tereta" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "Nema obrade" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "Broj akcija" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "Broj grešaka" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Broj poruka koje zahtijevaju aktivnost" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Broj poruka sa greškama pri isporuci" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "Korisni teret" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "Korisni teret tekst" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tip korisnog tereta" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "Objavi poruku" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "Zapis" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "Korisni teret zapisa" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "Zapis v0" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "Res" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "Odgovor" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "Odgovorni korisnik" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "Rezultat" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "Vrsta rezultata" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "Tip rezultata" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "Uzorak" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "Uzorak korisnog tereta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "Sekvenca" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "Status" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "URL spoljašnjeg AI sistema na koji se ovaj most povezuje." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "Model sa kojim je ovaj most povezan." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "Tip autentifikacije koji se koristi za povezivanje sa spoljašnjim AI sistemom." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "Korisnik koji će biti prikazan pri izvršavanju ovog AI mosta." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "Nit" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "Token autentifikacija" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "Token nije dozvoljen za ovo izvršavanje." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Vrsta aktivnosti iznimke na zapisu." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "URL" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "Nepodržan tip autentifikacije." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "Upotreba" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "Korisnik" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "Korisničke grupe kojima je dozvoljeno korišćenje ovog AI mosta." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "Poruke sa website-a" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "Povijest komunikacije Web stranice" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es.po b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es.po new file mode 100644 index 0000000..fbc7b5a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es.po @@ -0,0 +1,754 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-15 22:35+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" +"\n" +" Define cómo se procesa el resultado del sistema de IA.\n" +" - 'Inmediato': el resultado se procesa inmediatamente después de que " +"el sistema de IA responda.\n" +" - 'Asíncrono': El resultado se procesa en segundo plano.\n" +" Permite operaciones más largas.\n" +" Odoo proporcionará una URL al sistema de IA donde se enviará la " +"respuesta.\n" +" Los usuarios recibirán una notificación cuando se inicie la " +"operación.\n" +" No se enviará ninguna notificación cuando finalice.\n" +" " + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "%s ejecutado correctamente." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "%s falló." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "%s no está activo." + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "Puente de IA ejecutado" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "Ejecución de Puente de IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "Puente de IA fallo" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "Puente de IA inactivo" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "Mezcla de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "Crear hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "Eliminar hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "Escribir hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "Acción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "Acción necesaria" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "Activo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "Actividades" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Decoración de excepción de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "Estado de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icono de tipo de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configuración de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "Información de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "Uso de IA" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "Archivado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "Tiempo de espera asíncrono" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "Asincrónica" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "Conteo de anexos" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "Contraseña de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "Token de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "Nombre de usuario de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "Tipo de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "Autenticación básica" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "Compañía" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Define cómo se utiliza este puente. Si es 'Hilo', se usará en el contexto " +"del hilo de correo." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "Define el tipo de resultado esperado del sistema de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "Descripción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "Hecho" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "Borrador" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "Error" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "Ejecución" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "Recuento de ejecuciones" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "Detalles de ejecución" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "La ejecución ha expirado." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "No se ha encontrado la ejecución." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "Fecha de expiración" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "Fecha de expiración del token de operación asincrónica." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "Campo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "Campos que se incluirán en el puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "Filtrar" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "Seguidores" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguidores (Socios)" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Icono Font awesome, por ejemplo fa-tasks" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "Grupo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "Tiene mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "ID" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "Icono" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icono para indicar una actividad de excepción." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Si está marcada, nuevos mensajes requieren su atención." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Si se activa, algunos mensajes tienen un error de envío." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "Inmediato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "Es un hilo del puente IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "Es seguidor" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "Adjunto principal" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "Error de entrega del mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "Mensajes" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "Modelo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "Nombre del modelo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "Modelo requerido" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Mi plazo de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "Nombre" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Fecha límite para la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "Resumen de la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "Tipo de la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "Sin carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "Sin procesamiento" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "Ninguno" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "Número de acciones" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "Número de errores" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Número de mensajes que requieren una acción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Número de mensajes con error de entrega" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "Carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "Carga útil de texto" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo de carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "Deja un mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "Registro" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "Carga útil de registro" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "Registro v0" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "Respuesta" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "Respuesta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "Usuario responsable" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "Resultado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "Tipo de resultado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "Tipo de resultado" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "Ejemplo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "Carga útil de muestra" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" +"Carga útil de muestra que se enviará al sistema de IA. Esto se utiliza con " +"fines de prueba y depuración." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "Estado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Estado basado en actividades\n" +"Vencida: La fecha de vencimiento ya ha pasado\n" +"Hoy: La fecha de la actividad es hoy\n" +"Planificada: Actividades futuras." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "La URL del sistema de IA externo al que se conecta este puente." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "El modelo al que se asocia este puente." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "" +"El tipo de autenticación utilizado para conectarse al sistema de IA externo." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "El usuario que se mostrará al ejecutar este puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "Hilo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" +"Tiempo de espera en segundos para operaciones asincrónicas. Si la operación " +"no se completa dentro de este tiempo, se considerará fallida." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "Autenticación de token" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "Token no está permitido para esta ejecución." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Tipo de la actividad de excepción registrada." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "Dirección URL" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "Tipo de autenticación no admitido." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "Uso" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "Usuario" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "Grupos de usuarios autorizados a utilizar este puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "Mensajes del sitio web" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "Historia de la comunicación en la web" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" +"Cuando el uso es \"Hilo Puente IA\", el tipo de carga útil debe ser " +"\"Sin carga útil\"." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es_VE.po b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es_VE.po new file mode 100644 index 0000000..f217cf5 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/es_VE.po @@ -0,0 +1,754 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 01:25+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" +"\n" +" Define cómo se procesa el resultado del sistema de IA.\n" +" - 'Inmediato': el resultado se procesa inmediatamente después de que " +"el sistema de IA responda.\n" +" - 'Asíncrono': El resultado se procesa en segundo plano.\n" +" Permite operaciones más largas.\n" +" Odoo proporcionará una URL al sistema de IA donde se enviará la " +"respuesta.\n" +" Los usuarios recibirán una notificación cuando se inicie la " +"operación.\n" +" No se enviará ninguna notificación cuando finalice.\n" +" " + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "%s ejecutado correctamente." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "%s falló." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "%s no está activo." + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "Puente de IA ejecutado" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "Ejecución de Puente de IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "Puente de IA fallo" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "Puente de IA inactivo" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "Mezcla de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "Crear hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "Eliminar hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "Escribir hilo de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "Acción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "Acción necesaria" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "Activo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "Actividades" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Decoración de excepción de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "Estado de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icono de tipo de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configuración de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "Información de puente de IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "Uso de IA" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "Archivado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "Tiempo de espera asíncrono" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "Asincrónica" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "Conteo de anexos" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "Contraseña de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "Token de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "Nombre de usuario de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "Tipo de autenticación" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "Autenticación básica" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "Compañía" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Define cómo se utiliza este puente. Si es 'Hilo', se usará en el contexto " +"del hilo de correo." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "Define el tipo de resultado esperado del sistema de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "Descripción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "Hecho" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "Borrador" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "Hilo de correo electrónico" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "Error" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "Ejecución" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "Recuento de ejecuciones" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "Detalles de ejecución" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "La ejecución ha expirado." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "No se ha encontrado la ejecución." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "Fecha de expiración" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "Fecha de expiración del token de operación asincrónica." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "Campo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "Campos que se incluirán en el puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "Filtrar" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "Seguidores" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguidores (Socios)" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Icono Font awesome, por ejemplo fa-tasks" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "Grupo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "Tiene mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "ID" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "Icono" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icono para indicar una actividad de excepción." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Si está marcada, nuevos mensajes requieren su atención." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Si se activa, algunos mensajes tienen un error de envío." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "Inmediato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "Es un hilo del puente IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "Es seguidor" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "Adjunto principal" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "Error de entrega del mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "Mensajes" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "Modelo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "Nombre del modelo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "Modelo requerido" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "Modelos" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Mi plazo de actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "Nombre" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Fecha límite para la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "Resumen de la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "Tipo de la próxima actividad" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "Sin carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "Sin procesamiento" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "Ninguno" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "Número de acciones" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "Número de errores" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Número de mensajes que requieren una acción" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Número de mensajes con error de entrega" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "Carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "Carga útil de texto" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo de carga útil" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "Deja un mensaje" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "Registro" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "Carga útil de registro" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "Registro v0" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "Respuesta" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "Respuesta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "Usuario responsable" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "Resultado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "Tipo de resultado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "Tipo de resultado" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "Ejemplo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "Carga útil de muestra" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" +"Carga útil de muestra que se enviará al sistema de IA. Esto se utiliza con " +"fines de prueba y depuración." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "Estado" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Estado basado en actividades\n" +"Vencida: La fecha de vencimiento ya ha pasado\n" +"Hoy: La fecha de la actividad es hoy\n" +"Planificada: Actividades futuras." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "La URL del sistema de IA externo al que se conecta este puente." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "El modelo al que se asocia este puente." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "" +"El tipo de autenticación utilizado para conectarse al sistema de IA externo." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "El usuario que se mostrará al ejecutar este puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "Hilo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" +"Tiempo de espera en segundos para operaciones asincrónicas. Si la operación " +"no se completa dentro de este tiempo, se considerará fallida." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "Autenticación de token" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "Token no está permitido para esta ejecución." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Tipo de la actividad de excepción registrada." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "Dirección URL" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "Tipo de autenticación no admitido." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "Uso" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "Usuario" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "Grupos de usuarios autorizados a utilizar este puente de IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "Mensajes del sitio web" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "Historia de la comunicación en la web" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" +"Cuando el uso es \"Hilo Puente IA\", el tipo de carga útil debe ser " +"\"Sin carga útil\"." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/it.po b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/it.po new file mode 100644 index 0000000..207bdb4 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/it.po @@ -0,0 +1,754 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-31 10:59+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI " +"system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will " +"be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" +"\n" +" Definisce come il risultato dal sistema IA è elaborato.\n" +" - 'Immediato': il risultato è elaborato immediatamente dopo la " +"risposta della IA.\n" +" - 'Asincrono': il risultato è elaborato in background.\n" +" Consente operazioni più lunghe.\n" +" Odoo fornirà un URL al sistema IA dove la risposta verrà inviata.\n" +" Gli utenti riceveranno una notifica quando si avvia l'operazione.\n" +" Non verrà inviata una notifica alla conclusione.\n" +" " + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "%s eseguito con successo." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "%s fallito." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "%s non è attivo." + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "Collegamento IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "Collegamento IA eseguito" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "Esecuzione collegamento IA" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "Collegamento IA fallito" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "Collegamento IA inattivo" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "Mixin bridge IA" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "Esecuzione IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "Creazione thread IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "Rilascio thread IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "Scrittura thread IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "Azione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "Azione richiesta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "Attivo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "Attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Decorazione eccezione attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "Stato attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "Icona tipo attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "Collegamento IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configurazione collegamento IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "Informazioni collegamento IA" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Esecuzione IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "Utilizzo IA" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "In archivio" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "Timeout asincrono" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "Asincrono" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "Conteggio allegati" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "Password autenticazione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "Token autenticazione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "Nome utente autenticazione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "Tipo autenticazione" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "Autenticazione base" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "Azienda" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Definisce come viene usato questo collegamento. Se \"Discussione\", verrà " +"usato nel contesto della discussione e-mail." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "Definisce il tipo di risultato che ci si aspetta dal sistema IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "Descrizione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "Eseguito" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "Bozza" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "Discussione e-mail" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "Errore" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "Esecuzione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "Conteggio esecuzioni" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "Dettagli esecuzione" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "L'esecuzione è scaduta." + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "Esecuzione non trovata." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "Data di scadenza" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "Data scadenza per il token operazione asincrono." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "Campo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "Campi da includere nel collegamento IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "Filtro" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "Seguito da" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "Seguito da (partner)" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Icona Font Awesome es. fa-tasks" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "Gruppo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "Ha un messaggio" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "ID" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "Icona" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Icona per indicare un'attività eccezione." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Se selezionata, nuovi messaggi richiedono attenzione." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "Se selezionata, alcuni messaggi hanno un errore di consegna." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "Immediato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "È un thread IA" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "Segue" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "Allegato principale" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "Errore di consegna messaggio" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "Messaggi" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "Modello" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "Nome modello" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "Modello richiesto" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "Modelli" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Scadenza mia attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "Nome" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Scadenza prossima attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "Riepilogo prossima attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "Tipo prossima attività" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "Nessun carico" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "Nessuna elaborazione" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "Nessuno" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "Numero di azioni" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "Numero di errori" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Numero di messaggi che richiedono un'azione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Numero di messaggi con errore di consegna" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "Carico" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "Testo del carico" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo carico" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "Invia un messaggio" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "Record" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "Record carico" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "Record v0" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "Res" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "Risposta" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "Utente responsabile" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "Risultato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "Genere risultato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "Tipo risultato" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "Esempio" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "Carico esempio" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" +"Carico esempio da inviare al sistema IA. Questo è usato per attività di test " +"e debug." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "Stato" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Stato in base alle attività\n" +"Scaduto: la data richiesta è trascorsa\n" +"Oggi: la data attività è oggi\n" +"Pianificato: attività future." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "L'URL del sistema IA esterno a cui questo collegamento fa riferimento." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "Modello a cui è associato questo collegamento." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "" +"Il tipo di autenticazione utilizzato per connettere il sistema di IA esterno." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "Utente da visualizzare quando si esegue questo collegamento IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "Discussione" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" +"Timeout in secondi per operazioni asincrone. Se l'operazione non si completa " +"entro questo tempo, verrà considerata fallita." + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "Autenticazione token" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "Il token non è autorizzato per questa esecuzione." + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Tipo di attività eccezione sul record." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "URL" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "Tipo autenticazione non supportato." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "Uilizzo" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "Utente" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "Gruppi utente autorizzati ad usare questo collegamento IA." + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "Messaggi sito web" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "Cronologia comunicazioni sito web" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" +"Quando l'utilizzo è 'Rilascio thread IA', il tipo di carico deve essere " +"'Nessun carico'." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/pt_BR.po b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/pt_BR.po new file mode 100644 index 0000000..6631b26 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/i18n/pt_BR.po @@ -0,0 +1,727 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_kind +msgid "" +"\n" +" Defines how the result from the AI system is processed.\n" +" - 'Immediate': The result is processed immediately after the AI system responds.\n" +" - 'Asynchronous': The result is processed in the background.\n" +" It allows longer operations.\n" +" Odoo will provide a URL to the AI system where the response will be sent.\n" +" Users will receive a notification when the operation is started.\n" +" No notification will be sent when it is finished.\n" +" " +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s executed successfully." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s failed." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "%s is not active." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.module.category,name:ai_oca_bridge.module_category_ai +msgid "AI" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_act_window +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_menu +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_root_menu +msgid "AI Bridge" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Executed" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.ui.menu,name:ai_oca_bridge.ai_bridge_execution_menu +msgid "AI Bridge Execution" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Failed" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "AI Bridge Inactive" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_thread +msgid "AI Bridge Mixin" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.actions.act_window,name:ai_oca_bridge.ai_bridge_execution_act_window +msgid "AI Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_create +msgid "AI Thread Create" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_unlink +msgid "AI Thread Unlink" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__ai_thread_write +msgid "AI Thread Write" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__action +msgid "Action" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__active +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Active" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_ids +msgid "Activities" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_state +msgid "Activity State" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__ai_bridge_id +msgid "Ai Bridge" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_document_page__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_helpdesk_ticket_team__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_channel__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_cc__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_mail_thread_phone__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_phone_blacklist__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_partner__ai_bridge_info +#: model:ir.model.fields,field_description:ai_oca_bridge.field_res_users__ai_bridge_info +msgid "Ai Bridge Info" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__ai_usage +msgid "Ai Usage" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_search_view +msgid "Archived" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "Async Timeout" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__async +msgid "Asynchronous" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_password +msgid "Auth Password" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_token +msgid "Auth Token" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_username +msgid "Auth Username" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__auth_type +msgid "Authentication Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__basic +msgid "Basic Authentication" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__company_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__company_id +msgid "Company" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_uid +msgid "Created by" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__create_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__create_date +msgid "Created on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__result_type +msgid "Defines the type of result expected from the AI system." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__description +msgid "Description" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__display_name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__display_name +msgid "Display Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__done +msgid "Done" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__draft +msgid "Draft" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__error +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge_execution__state__error +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_ids +msgid "Execution" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__execution_count +msgid "Execution Count" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Execution Details" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution is expired." +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Execution not found." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration Date" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge_execution__expiration_date +msgid "Expiration date for the async operation token." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Field" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__field_ids +msgid "Fields to include in the AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__domain +msgid "Filter" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__group_ids +msgid "Group" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__has_message +msgid "Has Message" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__id +msgid "ID" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_kind__immediate +msgid "Immediate" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ir_model__is_ai_bridge_thread +msgid "Is Ai Bridge Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge____last_update +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution____last_update +msgid "Last Modified on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_uid +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__write_date +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__write_date +msgid "Last Updated on" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_main_attachment_id +msgid "Main Attachment" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_ids +msgid "Messages" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_id +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__model_id +msgid "Model" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model +msgid "Model Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__model_required +msgid "Model Required" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model,name:ai_oca_bridge.model_ir_model +msgid "Models" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__name +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__name +msgid "Name" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__none +msgid "No payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__none +msgid "No processing" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__none +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__none +msgid "None" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__payload_txt +msgid "Payload Txt" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__result_type__message +msgid "Post a Message" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record +msgid "Record" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Record Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__payload_type__record_v0 +msgid "Record v0" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__res_id +msgid "Res" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_execution_form_view +msgid "Response" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__result +msgid "Result" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_kind +msgid "Result Kind" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__result_type +msgid "Result Type" +msgstr "" + +#. module: ai_oca_bridge +#: model_terms:ir.ui.view,arch_db:ai_oca_bridge.ai_bridge_form_view +msgid "Sample" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "Sample Payload" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__sample_payload +msgid "" +"Sample payload to be sent to the AI system. This is used for testing and " +"debugging purposes." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__sequence +msgid "Sequence" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge_execution__state +msgid "State" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__url +msgid "The URL of the external AI system to which this bridge connects." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__model_id +msgid "The model to which this bridge is associated." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__auth_type +msgid "The type of authentication used to connect to the external AI system." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__user_id +msgid "The user that will be shown when executing this AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__usage__thread +msgid "Thread" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__async_timeout +msgid "" +"Timeout in seconds for asynchronous operations. If the operation does not " +"complete within this time, it will be considered failed." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields.selection,name:ai_oca_bridge.selection__ai_bridge__auth_type__token +msgid "Token Authentication" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/controllers/ai.py:0 +#, python-format +msgid "Token is not allowed for this execution." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__url +msgid "URL" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge_execution.py:0 +#, python-format +msgid "Unsupported authentication type." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__usage +msgid "Usage" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__user_id +msgid "User" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__group_ids +msgid "User groups allowed to use this AI bridge." +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,field_description:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: ai_oca_bridge +#: model:ir.model.fields,help:ai_oca_bridge.field_ai_bridge__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: ai_oca_bridge +#. odoo-python +#: code:addons/ai_oca_bridge/models/ai_bridge.py:0 +#, python-format +msgid "" +"When usage is 'AI Thread Unlink', the Payload Type must be 'No payload'." +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/__init__.py new file mode 100644 index 0000000..226066a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/__init__.py @@ -0,0 +1,5 @@ +from . import ai_bridge_thread +from . import ai_bridge +from . import ai_bridge_execution +from . import mail_thread +from . import ir_model diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge.py new file mode 100644 index 0000000..e520a49 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge.py @@ -0,0 +1,321 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 +import json +import logging +from datetime import date, datetime + +from odoo import _, api, fields, models +from odoo.tools.safe_eval import safe_eval + +_logger = logging.getLogger(__name__) + + +class AiBridge(models.Model): + + _name = "ai.bridge" + _inherit = ["mail.thread", "mail.activity.mixin"] + _description = "Ai Bridge Configuration" + _order = "sequence, id" + + sequence = fields.Integer( + default=10, + ) + company_id = fields.Many2one( + "res.company", + # We leave it empty to allow multiple companies to use the same bridge. + ) + usage = fields.Selection( + [ + ("none", "None"), + ("thread", "Thread"), + ("ai_thread_create", "AI Thread Create"), + ("ai_thread_write", "AI Thread Write"), + ("ai_thread_unlink", "AI Thread Unlink"), + ], + default="none", + help="Defines how this bridge is used. " + "If 'Thread', it will be used in the mail thread context.", + ) + name = fields.Char(required=True, translate=True) + active = fields.Boolean(default=True) + description = fields.Html(translate=True) + user_id = fields.Many2one( + "res.users", + default=lambda self: self.env.user, + help="The user that will be shown when executing this AI bridge.", + ) + payload_type = fields.Selection( + [ + ("none", "No payload"), + ("record", "Record"), + ("record_v0", "Record v0"), # Deprecated, use 'record' instead + ], + required=True, + store=True, + readonly=False, + compute="_compute_payload_type", + default="record", + ) + result_type = fields.Selection( + [ + ("none", "No processing"), + ("message", "Post a Message"), + ("action", "Action"), + ], + required=True, + default="none", + help="Defines the type of result expected from the AI system.", + ) + result_kind = fields.Selection( + [("immediate", "Immediate"), ("async", "Asynchronous")], + default="immediate", + help=""" + Defines how the result from the AI system is processed. + - 'Immediate': The result is processed immediately after the AI system responds. + - 'Asynchronous': The result is processed in the background. + It allows longer operations. + Odoo will provide a URL to the AI system where the response will be sent. + Users will receive a notification when the operation is started. + No notification will be sent when it is finished. + """, + ) + async_timeout = fields.Integer( + default=300, + help="Timeout in seconds for asynchronous operations. " + "If the operation does not complete within this time, it will be considered failed.", + ) + execution_ids = fields.One2many("ai.bridge.execution", "ai_bridge_id") + execution_count = fields.Integer( + compute="_compute_execution_count", + ) + url = fields.Char( + string="URL", + help="The URL of the external AI system to which this bridge connects.", + ) + auth_type = fields.Selection( + selection=[ + ("none", "None"), + ("basic", "Basic Authentication"), + ("token", "Token Authentication"), + ], + default="none", + string="Authentication Type", + help="The type of authentication used to connect to the external AI system.", + ) + auth_username = fields.Char(groups="base.group_system") + auth_password = fields.Char(groups="base.group_system") + auth_token = fields.Char(groups="base.group_system") + group_ids = fields.Many2many( + "res.groups", + help="User groups allowed to use this AI bridge.", + ) + sample_payload = fields.Text( + help="Sample payload to be sent to the AI system. " + "This is used for testing and debugging purposes.", + compute="_compute_sample_payload", + ) + model_id = fields.Many2one( + "ir.model", + string="Model", + required=False, + ondelete="cascade", + help="The model to which this bridge is associated.", + ) + model_required = fields.Boolean(compute="_compute_model_fields") + + ####################################### + # Payload type 'record' specific fields + ####################################### + + field_ids = fields.Many2many( + "ir.model.fields", + help="Fields to include in the AI bridge.", + compute="_compute_field_ids", + store=True, + readonly=False, + ) + model = fields.Char( + related="model_id.model", + string="Model Name", + ) + domain = fields.Char( + string="Filter", compute="_compute_domain", readonly=False, store=True + ) + + @api.onchange("usage") + def _compute_payload_type(self): + for record in self: + if record.usage == "ai_thread_unlink": + record.payload_type = "none" + + @api.constrains("usage", "payload_type") + def _check_payload_type_usage_compatibility(self): + for record in self: + if record.usage == "ai_thread_unlink" and record.payload_type != "none": + raise models.ValidationError( + _( + "When usage is 'AI Thread Unlink', " + "the Payload Type must be 'No payload'." + ) + ) + + @api.depends("usage") + def _compute_model_fields(self): + for record in self: + record.update(record._get_model_fields()) + + def _get_model_fields(self): + if self.usage == "thread": + return { + "model_required": True, + } + if self.usage in ["ai_thread_create", "ai_thread_write", "ai_thread_unlink"]: + return { + "model_required": True, + } + return { + "model_required": False, + } + + @api.depends("model_id") + def _compute_domain(self): + for record in self: + record.domain = "[]" + + @api.depends("model_id") + def _compute_field_ids(self): + for record in self: + record.field_ids = False + + @api.depends("field_ids", "model_id", "payload_type") + def _compute_sample_payload(self): + for record in self: + record.sample_payload = json.dumps( + record.with_context(sample_payload=True)._prepare_payload(), indent=4 + ) + + @api.depends("execution_ids") + def _compute_execution_count(self): + for record in self: + record.execution_count = len(record.execution_ids) + + def _get_info(self): + return {"id": self.id, "name": self.name, "description": self.description} + + def execute_ai_bridge(self, res_model, res_id): + self.ensure_one() + if not self.active or ( + self.group_ids and not self.env.user.groups_id & self.group_ids + ): + return { + "body": _("%s is not active.", self.name), + "args": {"type": "warning", "title": _("AI Bridge Inactive")}, + } + record = self.env[res_model].browse(res_id).exists() + if record: + execution = self.env["ai.bridge.execution"].create( + { + "ai_bridge_id": self.id, + "model_id": self.sudo().env["ir.model"]._get_id(res_model), + "res_id": res_id, + } + ) + result = execution._execute() + if result: + return result + if execution.state == "done": + return { + "notification": { + "body": _("%s executed successfully.", self.name), + "args": {"type": "success", "title": _("AI Bridge Executed")}, + } + } + return { + "notification": { + "body": _("%s failed.", self.name), + "args": {"type": "danger", "title": _("AI Bridge Failed")}, + } + } + + def _enabled_for(self, record): + """Check if the bridge is enabled for the given record.""" + self.ensure_one() + domain = safe_eval(self.domain) + if self.group_ids and not self.env.user.groups_id & self.group_ids: + return False + if domain: + return bool(record.filtered_domain(domain)) + return True + + def _prepare_payload(self, **kwargs): + method = getattr(self, f"_prepare_payload_{self.payload_type}", None) + if not method: + raise ValueError( + f"Unsupported payload type: {self.payload_type}. " + "Please implement a method for this payload type." + ) + return method(**kwargs) + + def _prepare_payload_none(self, res_model=False, res_id=False, **kwargs): + return { + "_model": res_model, + "_id": res_id, + } + + def _prepare_payload_record(self, record=None, **kwargs): + """Prepare the payload to be sent to the AI system.""" + self.ensure_one() + if not self.model_id: + return {} + if record is None and self.env.context.get("sample_payload"): + record = self.env[self.model_id.model].search([], limit=1) + if not record: + return {} + vals = {} + if self.sudo().field_ids: + vals = record.read(self.sudo().field_ids.mapped("name"))[0] + return json.loads( + json.dumps( + { + "record": vals, + "_model": record._name, + "_id": record.id, + }, + default=self.custom_serializer, + ) + ) + + def _prepare_payload_record_v0(self, record=None, **kwargs): + """Prepare the payload to be sent to the AI system.""" + _logger.warning( + "The 'record_v0' payload type is deprecated. " "Use 'record' instead." + ) + self.ensure_one() + if not self.model_id: + return {} + if record is None and self.env.context.get("sample_payload"): + record = self.env[self.model_id.model].search([], limit=1) + if not record: + return {} + vals = {} + if self.sudo().field_ids: + vals = record.read(self.sudo().field_ids.mapped("name"))[0] + return json.loads( + json.dumps( + { + **vals, + "_model": record._name, + "_id": record.id, + }, + default=self.custom_serializer, + ) + ) + + def custom_serializer(self, obj): + if isinstance(obj, datetime) or isinstance(obj, date): + return obj.isoformat() + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + raise TypeError(f"Type {type(obj)} not serializable") diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_execution.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_execution.py new file mode 100644 index 0000000..6f6247f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_execution.py @@ -0,0 +1,218 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json +import traceback +from datetime import timedelta +from io import StringIO + +import requests +from werkzeug import urls + +from odoo import _, api, fields, models, tools + + +class AiBridgeExecution(models.Model): + + _name = "ai.bridge.execution" + _description = "Ai Execution" + _order = "id desc" + + name = fields.Char( + store=True, + compute="_compute_name", + ) + + ai_bridge_id = fields.Many2one( + "ai.bridge", + required=True, + ondelete="cascade", + ) + res_id = fields.Integer(required=False) + state = fields.Selection( + [ + ("draft", "Draft"), + ("done", "Done"), + ("error", "Error"), + ], + default="draft", + required=True, + ) + model_id = fields.Many2one( + "ir.model", + required=False, + ondelete="cascade", + ) + payload = fields.Json(readonly=True) + payload_txt = fields.Text( + compute="_compute_payload_txt", + ) + result = fields.Text(readonly=True) + error = fields.Text(readonly=True) + company_id = fields.Many2one( + "res.company", + compute="_compute_company_id", + store=True, + readonly=True, + ) + expiration_date = fields.Datetime( + readonly=True, + help="Expiration date for the async operation token.", + ) + + @api.depends("model_id", "res_id", "ai_bridge_id") + def _compute_name(self): + for record in self: + model = record.sudo().model_id.name or "Unknown Model" + related = self.env[record.sudo().model_id.model].browse(record.res_id) + record.name = ( + f"{model} - {related.display_name} - {record.ai_bridge_id.name}" + ) + + @api.depends("payload") + def _compute_payload_txt(self): + for record in self: + if record.payload: + try: + record.payload_txt = json.dumps(record.payload, indent=4) + except (TypeError, ValueError): + record.payload_txt = str(record.payload) + else: + record.payload_txt = "" + + @api.depends("ai_bridge_id") + def _compute_company_id(self): + for record in self: + record.company_id = record.ai_bridge_id.company_id + + def _add_extra_payload_fields(self, payload): + """Add extra fields to the payload if needed.""" + self.ensure_one() + if self.ai_bridge_id.result_kind == "async": + self.expiration_date = fields.Datetime.now() + timedelta( + seconds=self.ai_bridge_id.async_timeout + ) + token = self._generate_token() + payload["_response_url"] = urls.url_join( + self.get_base_url(), f"/ai/response/{self.id}/{token}" + ) + IrParamSudo = self.env["ir.config_parameter"].sudo() + dbuuid = IrParamSudo.get_param("database.uuid") + db_create_date = IrParamSudo.get_param("database.create_date") + payload["_odoo"] = { + "db": dbuuid, + "db_name": self.env.cr.dbname, + "db_hash": tools.hmac( + self.env(su=True), + "database-hash", + (dbuuid, db_create_date, self.env.cr.dbname), + ), + "user_id": self.env.user.id, + } + return payload + + def _execute(self, **kwargs): + self.ensure_one() + record = None + if self.res_id and self.model_id: + record = self.env[self.sudo().model_id.model].browse(self.res_id) + payload = self.ai_bridge_id._prepare_payload( + record=record, + res_id=self.res_id, + model=self.sudo().model_id.model, + **kwargs, + ) + payload = self._add_extra_payload_fields(payload) + try: + response = requests.post( + self.ai_bridge_id.url, + json=payload, + auth=self._get_auth(), + headers=self._get_headers(), + timeout=30, # Default timeout, can be overridden by _execute_kwargs + **self._execute_kwargs(**kwargs), + ) + self.result = response.content + response.raise_for_status() + self.state = "done" + self.payload = payload + if self.ai_bridge_id.result_kind == "immediate": + return self._process_response(response.json()) + except Exception: + self.state = "error" + self.payload = payload + buff = StringIO() + traceback.print_exc(file=buff) + self.error = buff.getvalue() + buff.close() + + def _execute_kwargs(self, timeout=False, **kwargs): + self.ensure_one() + result = {} + if timeout: + result["timeout"] = timeout + return result + + def _get_auth(self): + """Return authentication for the request.""" + if self.ai_bridge_id.auth_type == "none": + return None + elif self.ai_bridge_id.auth_type == "basic": + return ( + self.ai_bridge_id.sudo().auth_username, + self.ai_bridge_id.sudo().auth_password, + ) + elif self.ai_bridge_id.auth_type == "token": + return {"Authorization": f"Bearer {self.ai_bridge_id.sudo().auth_token}"} + else: + raise ValueError(_("Unsupported authentication type.")) + + def _get_headers(self): + """Return headers for the request.""" + return { + "Content-Type": "application/json", + "Accept": "application/json", + } + + def _generate_token(self): + """Generate a token for async operations.""" + self.ensure_one() + return tools.hmac( + self.env(su=True), + "ai_bridge-access_token", + ( + self.id, + self.expiration_date and self.expiration_date.isoformat() or "expired", + ), + ) + + def _process_response(self, response): + """Process the response from the AI bridge.""" + self.ensure_one() + self.expiration_date = None + return getattr( + self.with_user(self.ai_bridge_id.user_id.id), + f"_process_response_{self.ai_bridge_id.result_type}", + self._process_response_none, + )(response) + + def _process_response_none(self, response): + return {} + + def _process_response_message(self, response): + return {"id": self._get_channel().message_post(**response).id} + + def _process_response_action(self, response): + if response.get("action"): + action = self.env["ir.actions.actions"]._for_xml_id(response["action"]) + if response.get("context"): + action["context"] = response["context"] + if response.get("res_id"): + action["res_id"] = response["res_id"] + return {"action": action} + return {} + + def _get_channel(self): + if self.model_id and self.res_id: + return self.env[self.model_id.model].browse(self.res_id) + return None diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_thread.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_thread.py new file mode 100644 index 0000000..888dbb7 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ai_bridge_thread.py @@ -0,0 +1,77 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, models + +_logger = logging.getLogger(__name__) + + +class AiBridgeThread(models.AbstractModel): + _name = "ai.bridge.thread" + _description = "AI Bridge Mixin" + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + model_id = self.sudo().env["ir.model"]._get_id(self._name) + for bridge in self.env["ai.bridge"].search( + [("model_id", "=", model_id), ("usage", "=", "ai_thread_create")] + ): + for record in records: + if bridge._enabled_for(record): + try: + bridge.execute_ai_bridge(record._name, record.id) + except Exception as e: + _logger.error( + "Error creating AI thread for creation on %s: %s", + record, + e, + ) + return records + + def write(self, values): + result = super().write(values) + model_id = self.sudo().env["ir.model"]._get_id(self._name) + for bridge in self.env["ai.bridge"].search( + [("model_id", "=", model_id), ("usage", "=", "ai_thread_write")] + ): + for record in self: + if bridge._enabled_for(record): + try: + bridge.execute_ai_bridge(record._name, record.id) + except Exception as e: + _logger.error( + "Error writing AI thread for writing on %s: %s", + record, + e, + ) + return result + + def unlink(self): + model_id = self.sudo().env["ir.model"]._get_id(self._name) + executions = self.env["ai.bridge.execution"] + for bridge in self.env["ai.bridge"].search( + [("model_id", "=", model_id), ("usage", "=", "ai_thread_unlink")] + ): + for record in self: + if bridge._enabled_for(record): + executions |= self.env["ai.bridge.execution"].create( + { + "ai_bridge_id": bridge.id, + "model_id": model_id, + "res_id": record.id, + } + ) + result = super().unlink() + for execution in executions: + try: + execution._execute() + except Exception as e: + _logger.error( + "Error executing AI thread unlink for %s: %s", + self, + e, + ) + return result diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ir_model.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ir_model.py new file mode 100644 index 0000000..9b1ba97 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/ir_model.py @@ -0,0 +1,28 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class IrModel(models.Model): + + _inherit = "ir.model" + + is_ai_bridge_thread = fields.Boolean() + ai_usage = fields.Char(store=False, search="_search_ai_usage") + + def _reflect_model_params(self, model): + vals = super(IrModel, self)._reflect_model_params(model) + vals["is_ai_bridge_thread"] = ( + isinstance(model, self.pool["ai.bridge.thread"]) and not model._abstract + ) + return vals + + def _search_ai_usage(self, operator, value): + if operator not in ("="): + return [] + if value == "thread": + return [("is_mail_thread", "=", True), ("transient", "=", False)] + if value in ["ai_thread_create", "ai_thread_write", "ai_thread_unlink"]: + return [("is_ai_bridge_thread", "=", True), ("transient", "=", False)] + return [] diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/mail_thread.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/mail_thread.py new file mode 100644 index 0000000..f3a3f90 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/models/mail_thread.py @@ -0,0 +1,68 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from lxml import etree + +from odoo import api, fields, models +from odoo.tools.misc import frozendict + + +class MailThread(models.AbstractModel): + _inherit = "mail.thread" + + ai_bridge_info = fields.Json(compute="_compute_ai_bridge_info", store=False) + + @api.depends() + def _compute_ai_bridge_info(self): + for record in self: + record.ai_bridge_info = [ + bridge._get_info() for bridge in record._get_ai_bridge_info() + ] + + def _get_ai_bridge_info(self): + self.ensure_one() + model_id = self.env["ir.model"].sudo().search([("model", "=", self._name)]).id + return ( + self.env["ai.bridge"] + .search([("model_id", "=", model_id), ("usage", "=", "thread")]) + .filtered(lambda r: r._enabled_for(self)) + ) + + @api.model + def get_view(self, view_id=None, view_type="form", **options): + res = super().get_view(view_id=view_id, view_type=view_type, **options) + if view_type == "form": + View = self.env["ir.ui.view"] + if view_id and res.get("base_model", self._name) != self._name: + View = View.with_context(base_model_name=res["base_model"]) + doc = etree.XML(res["arch"]) + + # We need to copy, because it is a frozen dict + all_models = res["models"].copy() + for node in doc.xpath("/form/div[hasclass('oe_chatter')]"): + # _add_tier_validation_label process + new_node = etree.fromstring( + "" + ) + new_arch, new_models = View.postprocess_and_fields(new_node, self._name) + new_node = etree.fromstring(new_arch) + for model in list(filter(lambda x: x not in all_models, new_models)): + if model not in res["models"]: + all_models[model] = new_models[model] + else: + all_models[model] = res["models"][model] + node.addprevious(new_node) + res["arch"] = etree.tostring(doc) + res["models"] = frozendict(all_models) + return res + + @api.model + def _get_view_fields(self, view_type, models): + """ + We need to add this in order to fix the usage of form opening from + trees inside a form + """ + result = super()._get_view_fields(view_type, models) + if view_type == "form": + result[self._name].add("ai_bridge_info") + return result diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONFIGURE.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONFIGURE.md new file mode 100644 index 0000000..fe6bdb9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONFIGURE.md @@ -0,0 +1,68 @@ +As an administrator access `AI Bridge\AI Bridge`. + +Create a new bridge. +Define the name, model, url and configuration. + +In order to improve the view of the AI configuration, use groups and domain to set better filters. + +## Payload Configuration + +On the external system, you will receive a POST payload. The data included will be the following: + +### General + +- _odoo: Standard data to identify the Odoo Database +- _model: Model of the related object +- _id: Id of the related object +- _response_url: Url to call with the response in case of async calls + +### Record Payload + + +Adds a new item called record with all the fields. + +### Record Payload (v0) + +Adds all the fields directly on the payload. +It will be removed on 17.0. + +## Asynchronous and synchronous calls + +The new system allows asynchronous and synchronous calls. +Asynchronous calls makes sense when the task to be processed don't need to be immediate. +For example, reviewing an invoice and leave a comment with the result. +The same would happen with a chat message. +We expect that the system will leave time to the AI to answer and Odoo's user can do other things. + +Meanwhile, Synchronous calls will froze odoo system and wait for an answer. +This makes sense when we expect some feedback from odoo user. +It makes sense, when we open an action for example. + +In the synchronous call, the result is processed when the AI system answers on the webhook. +On the other hand, it will be processed automatically on the synchronous call. + +## Result processing + +With the answers of the system we expect to do something about it. +We have the following options: + +### No processing + +In this case, the result will do nothing + +### Post a Message + +We will post a message on the original thread of the system. +The thread is computed by a function, so it can be overriden in future modules. +It expects the keyword arguments of the `message_post` function. + +### Action + +It expects to launch an action on the user interface. +It only makes sense on synchronous calls. + +It expects an action item with the following parameters: + +- action: xmlid of the action +- context: Context to pass to the action (not required) +- res_id: Id of the resource (not required) \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTEXT.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTEXT.md new file mode 100644 index 0000000..84deea9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTEXT.md @@ -0,0 +1,12 @@ +Right now, there are 2 different approaches for AI integration with Odoo: + +1. Make everything inside Odoo. +2. Make it using other tools and integrate Odoo with these tools. + +IMO, it would be better to make use of option 2 for different reasons: + +- Odoo server is intended as a transactional system. AI systems requires other kind of characteristics +- Everything changes too fast. I am not confident that Odoo can keep the pace in this topic +- There are OSS tools that fills the gap perfectly and are created just for this topic. + +Anyway, OCA is open to everyone and we don't intend to force an opinionated way of doing. For this reason, we have this module, that can be used as Bridge with AI systems. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTRIBUTORS.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..7a0afbb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/CONTRIBUTORS.md @@ -0,0 +1,8 @@ +- [Dixmit](https://www.dixmit.com) + + - Enric Tobella + +- [Sygel Technology](https://www.sygel.es) + + - Valentín Vinagre + \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/DESCRIPTION.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/DESCRIPTION.md new file mode 100644 index 0000000..0ebfc86 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module is used to create a bridge between Odoo and other AI systems like n8n. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/ROADMAP.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/ROADMAP.md new file mode 100644 index 0000000..a95064f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- Define examples to use and import +- Allow child fields. Right now, only first level fields are accepted. +- Information popover is not working properly when there is large data. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/USAGE.md new file mode 100644 index 0000000..8aa474f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/readme/USAGE.md @@ -0,0 +1,3 @@ +Use the bolt widget in the chatter to execute the different AI options. + +The options will be filtered according to the configuration. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/ir.model.access.csv b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/ir.model.access.csv new file mode 100644 index 0000000..bb6f5ff --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ai_bridge_user,ai_bridge.user,model_ai_bridge,base.group_user,1,0,0,0 +access_ai_bridge_manager,ai_bridge.manager,model_ai_bridge,base.group_system,1,1,1,0 +access_ai_bridge_execution_user,ai_bridge.user,model_ai_bridge_execution,base.group_user,1,1,1,0 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/security.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/security.xml new file mode 100644 index 0000000..fa72c29 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/security/security.xml @@ -0,0 +1,19 @@ + + + + AI Bridge Multi Company Rule + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + + + AI Bridge Multi Company Rule + + ['|',('company_id','=',False),('company_id', 'in', company_ids)] + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.png b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4adee3cffbbe51372c56582e1a879ec08b5e0d94 GIT binary patch literal 72370 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 literal 0 HcmV?d00001 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.svg b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.svg new file mode 100644 index 0000000..90cc269 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/icon.svg @@ -0,0 +1,49 @@ + + + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/index.html b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/index.html new file mode 100644 index 0000000..8aa403a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/description/index.html @@ -0,0 +1,560 @@ + + + + + +README.rst + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter/chatter.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter/chatter.esm.js new file mode 100644 index 0000000..418b53a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter/chatter.esm.js @@ -0,0 +1,32 @@ +/** @odoo-module **/ + +import {registerPatch} from "@mail/model/model_core"; + +registerPatch({ + name: "Chatter", + recordMethods: { + async onClickAiBridge(aiBridge) { + const saved = await this.doSaveRecord(); + if (!saved) { + return; + } + const result = await this.env.services.orm.call( + "ai.bridge", + "execute_ai_bridge", + [[aiBridge.id], this.thread.model, this.thread.id] + ); + if (result.action && this.env.services && this.env.services.action) { + this.env.services.action.doAction(result.action); + } else if ( + result.notification && + this.env.services && + this.env.services.notification + ) { + this.env.services.notification.add( + result.notification.body, + result.notification.args + ); + } + }, + }, +}); diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar/chatter_topbar.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar/chatter_topbar.xml new file mode 100644 index 0000000..01f7766 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar/chatter_topbar.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.esm.js new file mode 100644 index 0000000..80ed03b --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.esm.js @@ -0,0 +1,24 @@ +/** @odoo-module **/ + +import {ChatterAIItem} from "../chatter_topbar_ai_item/chatter_topbar_ai_item.esm"; +const {Component} = owl; +import {Dropdown} from "@web/core/dropdown/dropdown"; +import {DropdownItem} from "@web/core/dropdown/dropdown_item"; +import {registerMessagingComponent} from "@mail/utils/messaging_component"; + +export class ChatterAITopbar extends Component { + /** + * @returns {ChatterAITopbar} + */ + get chatterTopbar() { + return this.props.record; + } +} + +Object.assign(ChatterAITopbar, { + props: {record: Object}, + components: {Dropdown, DropdownItem, ChatterAIItem}, + template: "ai_oca_bridge.ChatterAITopbar", +}); + +registerMessagingComponent(ChatterAITopbar); diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.xml new file mode 100644 index 0000000..f0a40e6 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai/chatter_topbar_ai.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.esm.js new file mode 100644 index 0000000..b867db9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.esm.js @@ -0,0 +1,46 @@ +/** @odoo-module **/ + +const {Component, markup} = owl; +import {usePopover} from "@web/core/popover/popover_hook"; + +export class ChatterAIItemPopover extends Component {} +ChatterAIItemPopover.template = "ai_oca_bridge.ChatterAIItemPopover"; + +export class ChatterAIItem extends Component { + setup() { + super.setup(); + this.popover = usePopover(); + this.tooltipPopover = null; + } + get tooltipInfo() { + return { + help: markup(this.props.bridge.description || ""), + }; + } + onMouseEnter(ev) { + this.closeTooltip(); + this.tooltipPopover = this.popover.add( + ev.currentTarget, + ChatterAIItemPopover, + this.tooltipInfo, + { + closeOnClickAway: true, + position: "top", + } + ); + } + + onMouseLeave() { + this.closeTooltip(); + } + + closeTooltip() { + if (this.tooltipPopover) { + this.tooltipPopover(); + this.tooltipPopover = null; + } + } +} + +ChatterAIItem.template = "ai_oca_bridge.ChatterAIItem"; +ChatterAIItem.props = {bridge: Object}; diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.xml new file mode 100644 index 0000000..9393a6f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/src/components/chatter_topbar_ai_item/chatter_topbar_ai_item.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/mock_server.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/mock_server.esm.js new file mode 100644 index 0000000..8a505e6 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/mock_server.esm.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ + +// ensure mail mock server is loaded first. +import "@mail/../tests/helpers/mock_server"; + +import {MockServer} from "@web/../tests/helpers/mock_server"; +import {patch} from "@web/core/utils/patch"; + +patch(MockServer.prototype, "ai_oca_bridge", { + async _performRPC(route, args) { + if (args.model === "ai.bridge" && args.method === "execute_ai_bridge") { + const record = this.models["ai.bridge"].records.filter( + (record) => record.id === args.args[0][0] + ); + if (record && record[0].result_type === "action") { + return { + action: { + type: "ir.actions.act_window", + res_model: "res.partner", + views: [[false, "tree"]], + }, + }; + } + return { + notification: { + body: "Mocked AI Bridge Response", + args: { + type: "info", + title: "AI Bridge Notification", + }, + }, + }; + } + return this._super(...arguments); + }, +}); diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/model_definition.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/model_definition.esm.js new file mode 100644 index 0000000..ee1a109 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/helpers/model_definition.esm.js @@ -0,0 +1,29 @@ +/** @odoo-module **/ + +import { + addModelNamesToFetch, + insertModelFields, + insertRecords, +} from "@bus/../tests/helpers/model_definitions_helpers"; + +addModelNamesToFetch(["ai.bridge"]); + +insertModelFields("res.partner", { + ai_bridge_info: {default: [], type: "json"}, +}); +insertModelFields("ai.bridge", { + result_type: { + default: "none", + type: "selection", + selection: [ + ["none", "None"], + ["action", "Action"], + ["notification", "Notification"], + ], + }, + name: {string: "Name", type: "char"}, +}); +insertRecords("ai.bridge", [ + {id: 1, name: "Test AI Bridge", result_type: "none"}, + {id: 2, name: "Test AI Bridge Action", result_type: "action"}, +]); diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/web/test_ai_oca_bridge.esm.js b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/web/test_ai_oca_bridge.esm.js new file mode 100644 index 0000000..7aad6bc --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/static/tests/web/test_ai_oca_bridge.esm.js @@ -0,0 +1,98 @@ +/** @odoo-module */ +/* global QUnit */ +/* + Copyright 2025 Dixmit + License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +*/ +import {start, startServer} from "@mail/../tests/helpers/test_utils"; +QUnit.module("ai_oca_bridge"); + +QUnit.test("AI Notification", async function (assert) { + const pyEnv = await startServer(); + const resPartnerId1 = pyEnv["res.partner"].create({ + ai_bridge_info: [ + {name: "AI 1", id: 1, description: "test1 description"}, + {name: "AI 2", id: 2}, + ], + }); + const views = { + "res.partner,false,form": `
+ +
+ + +
+ `, + }; + const {click, openView} = await start({serverData: {views}}); + await openView({ + res_id: resPartnerId1, + res_model: "res.partner", + views: [[false, "form"]], + }); + await assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_AIButton .ai_button_selection`) + .length, + 1, + "should have an AI button" + ); + await click(".o_ChatterTopbar_AIButton .ai_button_selection"); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_AIItem`).length, + 2, + "should have 2 AI Items" + ); + await click(document.querySelectorAll(".o_ChatterTopbar_AIItem")[0]); + assert.strictEqual( + document.querySelectorAll(`.o_notification_manager .o_notification`).length, + 1, + "should have 1 Notification after clicking on AI Item" + ); +}); + +QUnit.test("AI Action", async function (assert) { + const pyEnv = await startServer(); + const resPartnerId1 = pyEnv["res.partner"].create({ + ai_bridge_info: [ + {name: "AI 1", id: 1, description: "test1 description"}, + {name: "AI 2", id: 2}, + ], + }); + const views = { + "res.partner,false,form": `
+ +
+ + +
+ `, + "res.partner,false,tree": ` + + + `, + }; + const {click, openView} = await start({serverData: {views}}); + await openView({ + res_id: resPartnerId1, + res_model: "res.partner", + views: [[false, "form"]], + }); + await assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_AIButton .ai_button_selection`) + .length, + 1, + "should have an AI button" + ); + await click(".o_ChatterTopbar_AIButton .ai_button_selection"); + assert.strictEqual( + document.querySelectorAll(`.o_ChatterTopbar_AIItem`).length, + 2, + "should have 2 AI Items" + ); + await click(document.querySelectorAll(".o_ChatterTopbar_AIItem")[1]); + assert.strictEqual( + document.querySelectorAll(`.o_list_view`).length, + 1, + "should have 1 List View after clicking on AI Item with action" + ); +}); diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/__init__.py new file mode 100644 index 0000000..be5a273 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/__init__.py @@ -0,0 +1,4 @@ +from . import test_bridge +from . import test_frontend +from . import test_connection +from . import test_mixin diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/fake_models.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/fake_models.py new file mode 100644 index 0000000..784b666 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/fake_models.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class BridgeTest(models.Model): + _name = "bridge.test" + _inherit = "ai.bridge.thread" + _description = "Test Model for AI Bridge" + + name = fields.Char() diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_bridge.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_bridge.py new file mode 100644 index 0000000..7a06239 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_bridge.py @@ -0,0 +1,367 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json +from unittest import mock + +from odoo.tests.common import TransactionCase + + +class TestBridge(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.bridge = cls.env["ai.bridge"].create( + { + "name": "Test Bridge", + "model_id": cls.env.ref("base.model_res_partner").id, + "url": "https://example.com/api", + "auth_type": "none", + "usage": "thread", + } + ) + # We add this in order to simplify tests, as jsons will be filled. + cls.bridge_extra = cls.env["ai.bridge"].create( + { + "name": "Test Bridge Extra", + "model_id": cls.env.ref("base.model_res_partner").id, + "url": "https://example.com/api", + "auth_type": "none", + "usage": "thread", + } + ) + cls.partner = cls.env["res.partner"].create( + { + "name": "Test Partner", + "email": "test@example.com", + } + ) + cls.group = cls.env["res.groups"].create( + { + "name": "Test Group", + } + ) + + def test_bridge_none_auth(self): + self.assertEqual(self.bridge.auth_type, "none") + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertTrue( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertEqual(execution.res_id, self.partner.id) + self.assertNotIn("name", execution.payload) + + def test_bridge_none_auth_fields_record_v0(self): + self.bridge.write( + { + "payload_type": "record_v0", + "auth_type": "none", + "field_ids": [ + (4, self.env.ref("base.field_res_partner__name").id), + (4, self.env.ref("base.field_res_partner__create_date").id), + (4, self.env.ref("base.field_res_partner__image_1920").id), + ], + } + ) + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertTrue( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertEqual(execution.res_id, self.partner.id) + self.assertIn("name", execution.payload) + self.assertEqual(execution.payload["name"], self.partner.name) + self.assertEqual(1, self.bridge.execution_count) + + def test_bridge_none_auth_fields_record(self): + self.bridge.write( + { + "payload_type": "record", + "auth_type": "none", + "field_ids": [ + (4, self.env.ref("base.field_res_partner__name").id), + (4, self.env.ref("base.field_res_partner__create_date").id), + (4, self.env.ref("base.field_res_partner__image_1920").id), + ], + } + ) + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertTrue( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertEqual(execution.res_id, self.partner.id) + self.assertIn("name", execution.payload["record"]) + self.assertEqual(execution.payload["record"]["name"], self.partner.name) + self.assertEqual(1, self.bridge.execution_count) + + def test_bridge_basic_auth(self): + self.bridge.write( + { + "auth_type": "basic", + "auth_username": "test_user", + "auth_password": "test_pass", + } + ) + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertTrue( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + + def test_bridge_token_auth(self): + self.bridge.write( + { + "auth_type": "token", + "auth_token": "test_token", + } + ) + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertTrue( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + + def test_bridge_error(self): + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertTrue(execution) + self.assertTrue(execution.error) + + def test_bridge_unactive(self): + self.bridge.toggle_active() + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertFalse(execution) + + def test_bridge_check_group(self): + self.bridge.group_ids = [(4, self.group.id)] + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertFalse(execution) + + def test_bridge_domain_filtering(self): + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.bridge.write({"domain": f"[('id', '!=', {self.partner.id})]"}) + self.partner.invalidate_recordset() + self.assertNotIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + + def test_bridge_group_filtering(self): + self.assertTrue(self.partner.ai_bridge_info) + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.bridge.write({"group_ids": [(4, self.group.id)]}) + self.partner.invalidate_recordset() + self.assertNotIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + self.env.user.groups_id |= self.group + self.partner.invalidate_recordset() + self.assertIn( + self.bridge.id, [bridge["id"] for bridge in self.partner.ai_bridge_info] + ) + + def test_view_fields(self): + view = self.partner.get_view(view_type="form") + self.assertIn("ai_bridge_info", view["models"][self.partner._name]) + self.assertIn(b'name="ai_bridge_info"', view["arch"]) + + def test_sample(self): + self.assertTrue(self.bridge.sample_payload) + self.assertIn("_id", self.bridge.sample_payload) + + def test_bridge_result_message(self): + self.bridge.write({"result_type": "message"}) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + message_count = self.env["mail.message"].search_count( + [("model", "=", self.partner._name), ("res_id", "=", self.partner.id)] + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertEqual( + self.env["mail.message"].search_count( + [("model", "=", self.partner._name), ("res_id", "=", self.partner.id)] + ), + message_count + 1, + ) + + def test_bridge_result_message_async(self): + self.bridge.write({"result_type": "message", "result_kind": "async"}) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + message_count = self.env["mail.message"].search_count( + [("model", "=", self.partner._name), ("res_id", "=", self.partner.id)] + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertEqual( + self.env["mail.message"].search_count( + [("model", "=", self.partner._name), ("res_id", "=", self.partner.id)] + ), + message_count, + ) + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertTrue(execution.expiration_date) + execution._process_response({"body": "My message"}) + self.assertEqual( + self.env["mail.message"].search_count( + [("model", "=", self.partner._name), ("res_id", "=", self.partner.id)] + ), + message_count + 1, + ) + self.assertFalse(execution.expiration_date) + + def test_bridge_result_action_immediate(self): + self.bridge.write({"result_type": "action", "result_kind": "immediate"}) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, + json=lambda: { + "action": "ai_oca_bridge.ai_bridge_act_window", + "context": {"key": "value"}, + }, + ) + result = self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + self.assertIn("action", result) + self.assertEqual( + result["action"]["id"], + self.env.ref("ai_oca_bridge.ai_bridge_act_window").id, + ) + + def test_bridge_execute_computed_fields(self): + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + mock_post.assert_called_once() + execution = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge.id)] + ) + self.assertEqual( + execution.payload["_id"], json.loads(execution.payload_txt)["_id"] + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_connection.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_connection.py new file mode 100644 index 0000000..92ba5f9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_connection.py @@ -0,0 +1,116 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest import mock + +from werkzeug import urls + +from odoo.tests.common import HttpCase, tagged + + +@tagged("post_install", "-at_install") +class TestAsyncConnection(HttpCase): + def setUp(self): + super().setUp() + self.bridge = self.env["ai.bridge"].create( + { + "name": "Test Bridge", + "model_id": self.env.ref("base.model_res_partner").id, + "url": "https://example.com/api", + "auth_type": "none", + "result_type": "message", + "result_kind": "async", + "usage": "thread", + } + ) + self.partner = self.env["res.partner"].create( + { + "name": "Test Partner", + "email": "test@example.com", + } + ) + + with mock.patch("requests.post") as mock_post: + self.bridge.execute_ai_bridge(self.partner._name, self.partner.id) + self.url = mock_post.call_args[1]["json"]["_response_url"] + self.message_count = self.env["mail.message"].search_count( + [ + ("model", "=", self.partner._name), + ("res_id", "=", self.partner.id), + ] + ) + + def test_wrong_key(self): + result = self.opener.post(f"{self.url}1234", json={"body": "Test response"}) + self.assertEqual( + result.status_code, 404, "Should return 404 for wrong key in URL." + ) + + def test_wrong_id(self): + result = self.opener.post( + f"{self.base_url()}/ai/response/-1/TOKEN", json={"body": "Test response"} + ) + self.assertEqual( + result.status_code, 404, "Should return 404 for wrong key in URL." + ) + + def test_connection(self): + self.assertTrue( + self.env["ai.bridge.execution"].search( + [ + ("ai_bridge_id", "=", self.bridge.id), + ("expiration_date", "!=", False), + ] + ) + ) + self.opener.post(self.url, json={"body": "Test response"}) + self.assertEqual( + self.env["mail.message"].search_count( + [ + ("model", "=", self.partner._name), + ("res_id", "=", self.partner.id), + ] + ), + self.message_count + 1, + "A new message should be created in the thread.", + ) + self.assertFalse( + self.env["ai.bridge.execution"].search( + [ + ("ai_bridge_id", "=", self.bridge.id), + ("expiration_date", "!=", False), + ] + ) + ) + # Key is wrong, so no message should be created + result = self.opener.post(self.url, json={"body": "Test response"}) + self.assertEqual( + result.status_code, 404, "Should return 404 for wrong key in URL." + ) + + def test_connection_expired(self): + self.assertTrue( + self.env["ai.bridge.execution"].search( + [ + ("ai_bridge_id", "=", self.bridge.id), + ("expiration_date", "!=", False), + ] + ) + ) + execution = self.env["ai.bridge.execution"].search( + [ + ("ai_bridge_id", "=", self.bridge.id), + ("expiration_date", "!=", False), + ] + ) + execution.expiration_date = "2020-01-01 00:00:00" + token = execution._generate_token() + result = self.opener.post( + urls.url_join( + execution.get_base_url(), f"/ai/response/{execution.id}/{token}" + ), + json={"body": "Test response"}, + ) + self.assertEqual( + result.status_code, 404, "Should return 404 for expired execution." + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_frontend.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_frontend.py new file mode 100644 index 0000000..74947d6 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_frontend.py @@ -0,0 +1,12 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import common + + +@common.tagged("post_install", "-at_install") +class TestFrontend(common.HttpCase): + def test_javascript(self): + self.browser_js( + "/web/tests?module=ai_oca_bridge", "", login="admin", timeout=1800 + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_mixin.py b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_mixin.py new file mode 100644 index 0000000..afe76f5 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/tests/test_mixin.py @@ -0,0 +1,174 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo_test_helper import FakeModelLoader + +from odoo.exceptions import ValidationError +from odoo.tests.common import Form, TransactionCase + + +class TestBridge(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Load fake models ->/ + cls.loader = FakeModelLoader(cls.env, cls.__module__) + cls.loader.backup_registry() + from .fake_models import BridgeTest + + cls.loader.update_registry((BridgeTest,)) + cls.bridge = cls.env["ai.bridge"].create( + { + "name": "Test Bridge", + "model_id": cls.env["ir.model"]._get_id("bridge.test"), + "url": "https://example.com/api", + "auth_type": "none", + "usage": "none", + } + ) + + @classmethod + def tearDownClass(cls): + cls.loader.restore_registry() + super().tearDownClass() + + def test_bridge_thread_creation(self): + self.bridge.write({"usage": "ai_thread_create"}) + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"result": "success"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + # Create a test record + record = self.env["bridge.test"].create({"name": "Test Record"}) + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + mock_post.assert_called_once() + record.write({"name": "Updated Record"}) + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + record.unlink() + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + mock_post.assert_called_once() + + def test_bridge_thread_write(self): + self.bridge.write({"usage": "ai_thread_write"}) + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"result": "success"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + # Create a test record + record = self.env["bridge.test"].create({"name": "Test Record"}) + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + record.write({"name": "Updated Record"}) + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + record.unlink() + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + mock_post.assert_called_once() + + def test_bridge_thread_unlink(self): + self.assertNotEqual(self.bridge.payload_type, "none") + with Form(self.bridge) as bridge_form: + bridge_form.usage = "ai_thread_unlink" + self.assertEqual(self.bridge.payload_type, "none") + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"result": "success"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + # Create a test record + record = self.env["bridge.test"].create({"name": "Test Record"}) + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + record.write({"name": "Updated Record"}) + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + record.unlink() + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge.id)] + ), + ) + mock_post.assert_called_once() + + def test_bridge_thread_unlink_constrains(self): + self.assertNotEqual(self.bridge.payload_type, "none") + with Form(self.bridge) as bridge_form: + bridge_form.usage = "ai_thread_unlink" + self.assertEqual(self.bridge.payload_type, "none") + with self.assertRaises(ValidationError): + self.bridge.payload_type = "record" + + def test_bridge_model_search(self): + models = self.env["ir.model"].search([("ai_usage", "=", "thread")]) + model = self.env["ir.model"]._get_id("bridge.test") + self.assertTrue(models) + self.assertIn(self.env.ref("base.model_res_partner"), models) + self.assertNotIn(model, models.ids) + models = self.env["ir.model"].search([("ai_usage", "=", "ai_thread_create")]) + self.assertTrue(models) + self.assertNotIn(self.env.ref("base.model_res_partner"), models) + self.assertIn(model, models.ids) + models = self.env["ir.model"].search([("ai_usage", "=", "none")]) + self.assertTrue(models) + self.assertIn(self.env.ref("base.model_res_partner"), models) + self.assertIn(model, models.ids) + + def test_bridge_model_required(self): + self.assertFalse(self.bridge.model_required) + self.bridge.usage = "ai_thread_create" + self.assertTrue(self.bridge.model_required) + self.bridge.usage = "thread" + self.assertTrue(self.bridge.model_required) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge.xml new file mode 100644 index 0000000..a60ef4a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge.xml @@ -0,0 +1,149 @@ + + + + + + ai.bridge + +
+
+ + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + ai.bridge + + + + + + + + + + + + + + + ai.bridge + + + + + + + + + + + + + + AI Bridge + ai.bridge + tree,form + [] + {} + + + + AI Bridge + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge_execution.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge_execution.xml new file mode 100644 index 0000000..47c3e48 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/ai_bridge_execution.xml @@ -0,0 +1,88 @@ + + + + + + ai.bridge.execution + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + ai.bridge.execution + + + + + + + + + + + + + + + ai.bridge.execution + + + + + + + + + + + + + + AI Execution + ai.bridge.execution + tree,form + [] + {} + + + + AI Bridge Execution + + + + + +
diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/menu.xml b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/menu.xml new file mode 100644 index 0000000..ecc9272 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/ai_oca_bridge/views/menu.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/ARCHITECTURE.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/ARCHITECTURE.md new file mode 100644 index 0000000..744e0df --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/ARCHITECTURE.md @@ -0,0 +1,32 @@ +# Architecture + +```mermaid +flowchart TD + U[Users] -->|HTTP| V[Views and QWeb Templates] + V --> C[Controllers] + V --> W[Wizards – Transient Models] + C --> M[Models and ORM] + W --> M + M --> R[Reports] + DX[Data XML] --> M + S[Security – ACLs and Groups] -. enforces .-> M + + subgraph Ai_oca_bridge Module - ai_oca_bridge + direction LR + M:::layer + W:::layer + C:::layer + V:::layer + R:::layer + S:::layer + DX:::layer + end + + classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px +``` + +Notes +- Views include tree/form/kanban templates and report templates. +- Controllers provide website/portal routes when present. +- Wizards are UI flows implemented with `models.TransientModel`. +- Data XML loads data/demo records; Security defines groups and access. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONFIGURATION.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONFIGURATION.md new file mode 100644 index 0000000..7021900 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONFIGURATION.md @@ -0,0 +1,3 @@ +# Configuration + +Refer to Odoo settings for ai_oca_bridge. Configure related models, access rights, and options as needed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONTROLLERS.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONTROLLERS.md new file mode 100644 index 0000000..ff097c0 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/CONTROLLERS.md @@ -0,0 +1,17 @@ +# Controllers + +HTTP routes provided by this module. + +```mermaid +sequenceDiagram + participant U as User/Client + participant C as Module Controllers + participant O as ORM/Views + + U->>C: HTTP GET/POST (routes) + C->>O: ORM operations, render templates + O-->>U: HTML/JSON/PDF +``` + +Notes +- See files in controllers/ for route definitions. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/DEPENDENCIES.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/DEPENDENCIES.md new file mode 100644 index 0000000..ae607bf --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/DEPENDENCIES.md @@ -0,0 +1,5 @@ +# Dependencies + +This addon depends on: + +- [mail](../../odoo-bringout-oca-ocb-mail) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/FAQ.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/FAQ.md new file mode 100644 index 0000000..52f3d4d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/FAQ.md @@ -0,0 +1,4 @@ +# FAQ + +- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged). +- Q: How to enable? A: Start server with --addon ai_oca_bridge or install in UI. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/INSTALL.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/INSTALL.md new file mode 100644 index 0000000..4e0f683 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/INSTALL.md @@ -0,0 +1,7 @@ +# Install + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge" +# or +uv pip install odoo-bringout-oca-ai-ai_oca_bridge" +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/MODELS.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/MODELS.md new file mode 100644 index 0000000..011b90a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/MODELS.md @@ -0,0 +1,16 @@ +# Models + +Detected core models and extensions in ai_oca_bridge. + +```mermaid +classDiagram + class ai_bridge + class ai_bridge_execution + class ai_bridge_thread + class ir_model + class mail_thread +``` + +Notes +- Classes show model technical names; fields omitted for brevity. +- Items listed under _inherit are extensions of existing models. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/OVERVIEW.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/OVERVIEW.md new file mode 100644 index 0000000..20fb47e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/OVERVIEW.md @@ -0,0 +1,6 @@ +# Overview + +Packaged Odoo addon: ai_oca_bridge. Provides features documented in upstream Odoo 16 under this addon. + +- Source: OCA/OCB 16.0, addon ai_oca_bridge +- License: LGPL-3 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/REPORTS.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/REPORTS.md new file mode 100644 index 0000000..e0ea35f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/REPORTS.md @@ -0,0 +1,3 @@ +# Reports + +This module does not define custom reports. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/SECURITY.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/SECURITY.md new file mode 100644 index 0000000..53dc3b5 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/SECURITY.md @@ -0,0 +1,41 @@ +# Security + +Access control and security definitions in ai_oca_bridge. + +## Access Control Lists (ACLs) + +Model access permissions defined in: +- **[ir.model.access.csv](../ai_oca_bridge/security/ir.model.access.csv)** + - 3 model access rules + +## Record Rules + +Row-level security rules defined in: + +## Security Groups & Configuration + +Security groups and permissions defined in: +- **[security.xml](../ai_oca_bridge/security/security.xml)** + +```mermaid +graph TB + subgraph "Security Layers" + A[Users] --> B[Groups] + B --> C[Access Control Lists] + C --> D[Models] + B --> E[Record Rules] + E --> F[Individual Records] + end +``` + +Security files overview: +- **[ir.model.access.csv](../ai_oca_bridge/security/ir.model.access.csv)** + - Model access permissions (CRUD rights) +- **[security.xml](../ai_oca_bridge/security/security.xml)** + - Security groups, categories, and XML-based rules + +Notes +- Access Control Lists define which groups can access which models +- Record Rules provide row-level security (filter records by user/group) +- Security groups organize users and define permission sets +- All security is enforced at the ORM level by Odoo diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/TROUBLESHOOTING.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/TROUBLESHOOTING.md new file mode 100644 index 0000000..56853cb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/TROUBLESHOOTING.md @@ -0,0 +1,5 @@ +# Troubleshooting + +- Ensure Python and Odoo environment matches repo guidance. +- Check database connectivity and logs if startup fails. +- Validate that dependent addons listed in DEPENDENCIES.md are installed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/USAGE.md new file mode 100644 index 0000000..96d4511 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/USAGE.md @@ -0,0 +1,7 @@ +# Usage + +Start Odoo including this addon (from repo root): + +```bash +python3 scripts/nix_odoo_web_server.py --db-name mydb --addon ai_oca_bridge +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/doc/WIZARDS.md b/odoo-bringout-oca-ai-ai_oca_bridge/doc/WIZARDS.md new file mode 100644 index 0000000..48e790d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/doc/WIZARDS.md @@ -0,0 +1,3 @@ +# Wizards + +This module does not include UI wizards. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge/pyproject.toml b/odoo-bringout-oca-ai-ai_oca_bridge/pyproject.toml new file mode 100644 index 0000000..66c10e0 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge/pyproject.toml @@ -0,0 +1,42 @@ +[project] +name = "odoo-bringout-oca-ai-ai_oca_bridge" +version = "16.0.0" +description = "AI OCA Bridge - Makes a basic configuration to be used as bridge with external AI systems" +authors = [ + { name = "Ernad Husremovic", email = "hernad@bring.out.ba" } +] +dependencies = [ + "odoo-bringout-oca-ocb-mail>=16.0.0", + "requests>=2.25.1" +] +readme = "README.md" +requires-python = ">= 3.11" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business", +] + +[project.urls] +homepage = "https://github.com/bringout/0" +repository = "https://github.com/bringout/0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["ai_oca_bridge"] + +[tool.rye] +managed = true +dev-dependencies = [ + "pytest>=8.4.1", +] diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/README.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/README.md new file mode 100644 index 0000000..0b915d9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/README.md @@ -0,0 +1,44 @@ +# Ai Oca Bridge Chatter + +Odoo addon: ai_oca_bridge_chatter + +## Installation + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_chatter +``` + +## Dependencies + +This addon depends on: +- ai_oca_bridge + +## Manifest Information + +- **Name**: Ai Oca Bridge Chatter +- **Version**: 16.0.1.0.0 +- **Category**: N/A +- **License**: AGPL-3 +- **Installable**: False + +## Source + +Based on [OCA/ai](https://github.com/OCA/ai) branch 16.0, addon `ai_oca_bridge_chatter`. + +## License + +This package maintains the original AGPL-3 license from the upstream Odoo project. + +## Documentation + +- Overview: doc/OVERVIEW.md +- Architecture: doc/ARCHITECTURE.md +- Models: doc/MODELS.md +- Controllers: doc/CONTROLLERS.md +- Wizards: doc/WIZARDS.md +- Install: doc/INSTALL.md +- Usage: doc/USAGE.md +- Configuration: doc/CONFIGURATION.md +- Dependencies: doc/DEPENDENCIES.md +- Troubleshooting: doc/TROUBLESHOOTING.md +- FAQ: doc/FAQ.md diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/README.rst b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/README.rst new file mode 100644 index 0000000..686d1cf --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/README.rst @@ -0,0 +1,103 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +===================== +Ai Oca Bridge Chatter +===================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:eb5b0cdbee135ee2cd5731de3c50a697c02fb931d21800fcc5fa39ca670edad6 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github + :target: https://github.com/OCA/ai/tree/16.0/ai_oca_bridge_chatter + :alt: OCA/ai +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_oca_bridge_chatter + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows usage of LLM chatbots inside Odoo. + +The logic of the chatbot should be defined in an external system like +n8n. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On your external AI system create a workflow that will receive messages +and will return the call directly. + +Here you can see an `example <./static/description/Chat.json>`__ of +configuration in n8n. + +After that, create a bridge with usage type chatter and payload type +chatter. Then, create a user and assign the bridge to it. + +With this configuration, the user will answer automatically using the +external systema and will be online permanently. It can be used on +livechat without any issues. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Dixmit + +Contributors +------------ + +- `Dixmit `__ + + - Enric Tobella + +- `Binhex `__ + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/ai `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__manifest__.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__manifest__.py new file mode 100644 index 0000000..cca1029 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 202 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Ai Oca Bridge Chatter", + "summary": """Integrate a Bridge with a user that will use it on chatter""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Dixmit,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/ai", + "depends": [ + "ai_oca_bridge", + ], + "data": [ + "views/res_users.xml", + "views/ai_bridge.xml", + ], + "demo": [], +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/ai_oca_bridge_chatter.pot b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/ai_oca_bridge_chatter.pot new file mode 100644 index 0000000..bed7716 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/ai_oca_bridge_chatter.pot @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/bs.po b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/bs.po new file mode 100644 index 0000000..5a5d78d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/bs.po @@ -0,0 +1,86 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "AI most" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Konfiguracija Ai mosta" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ai izvršavanje" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "Razgovor" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "Korisnik razgovora" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "Kontakt" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "Kanal rasprave" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tip korisnog tereta" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "Poruka ne pripada nijednom kanalu." + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "Zapis mora biti instanca mail.message." + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "Upotreba" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "Korisnik" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es.po b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es.po new file mode 100644 index 0000000..9cec3c1 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es.po @@ -0,0 +1,91 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 01:25+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configuración de puente de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "Chatter" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "Usuario de Chatter" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Define cómo se utiliza este puente. Si es 'Hilo', se usará en el contexto " +"del hilo de correo." + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "Canal de discusión" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo de carga útil" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "El mensaje no pertenece a ningún canal." + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "El registro debe ser una instancia de mail.message." + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "Uso" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "Usuario" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es_VE.po b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es_VE.po new file mode 100644 index 0000000..f40923e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/es_VE.po @@ -0,0 +1,91 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 01:25+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "Puente de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configuración de puente de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Ejecución de IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "Chatter" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "Usuario de Chatter" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "Contacto" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Define cómo se utiliza este puente. Si es 'Hilo', se usará en el contexto " +"del hilo de correo." + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "Canal de discusión" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo de carga útil" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "El mensaje no pertenece a ningún canal." + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "El registro debe ser una instancia de mail.message." + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "Uso" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "Usuario" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/it.po b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/it.po new file mode 100644 index 0000000..10f4e59 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/it.po @@ -0,0 +1,91 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-29 09:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "Collegamento IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "Configurazione collegamento IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "Esecuzione IA" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "Elenco messaggi" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "Utente elenco messaggi" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "Contatto" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" +"Definisce come viene usato questo collegamento. Se \"Discussione\", verrà " +"usato nel contesto della discussione e-mail." + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "Canale discussione" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "Tipo carico" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "Questo messaggi non appartiene a nessun canale." + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "Il record deve essere una istanza mail.message." + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "Uilizzo" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "Utente" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/pt_BR.po b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/pt_BR.po new file mode 100644 index 0000000..ef68a5f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/i18n/pt_BR.po @@ -0,0 +1,87 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_chatter +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_res_users__ai_bridge_id +msgid "AI Bridge" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge +msgid "Ai Bridge Configuration" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_ai_bridge_execution +msgid "Ai Execution" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__payload_type__chatter +#: model:ir.model.fields.selection,name:ai_oca_bridge_chatter.selection__ai_bridge__usage__chatter +msgid "Chatter" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge_execution__chatter_user_id +msgid "Chatter User" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_partner +msgid "Contact" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,help:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "" +"Defines how this bridge is used. If 'Thread', it will be used in the mail " +"thread context." +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_mail_channel +msgid "Discussion Channel" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__payload_type +msgid "Payload Type" +msgstr "" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge_execution.py:0 +#, python-format +msgid "The message does not belong to any channel." +msgstr "" + +#. module: ai_oca_bridge_chatter +#. odoo-python +#: code:addons/ai_oca_bridge_chatter/models/ai_bridge.py:0 +#, python-format +msgid "The record must be a mail.message instance." +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model.fields,field_description:ai_oca_bridge_chatter.field_ai_bridge__usage +msgid "Usage" +msgstr "" + +#. module: ai_oca_bridge_chatter +#: model:ir.model,name:ai_oca_bridge_chatter.model_res_users +msgid "User" +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/__init__.py new file mode 100644 index 0000000..a6b8352 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/__init__.py @@ -0,0 +1,5 @@ +from . import ai_bridge +from . import ai_bridge_execution +from . import res_partner +from . import res_users +from . import mail_channel diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge.py new file mode 100644 index 0000000..fb6ca51 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge.py @@ -0,0 +1,32 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class AiBridge(models.Model): + _inherit = "ai.bridge" + + usage = fields.Selection( + selection_add=[("chatter", "Chatter")], ondelete={"chatter": "set default"} + ) + payload_type = fields.Selection( + selection_add=[("chatter", "Chatter")], ondelete={"chatter": "set default"} + ) + + def _prepare_payload_chatter(self, record=None, **kwargs): + if not record: + record = self.env["mail.message"].search([], limit=1) + if record._name != "mail.message": + raise ValueError(_("The record must be a mail.message instance.")) + return { + "message": { + "res_id": record.res_id, + "model": record.model, + "body": record.body, + "author_id": record.author_id.id, + "subject": record.subject, + "date": record.date.isoformat(), + "author_name": record.author_id.name, + } + } diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge_execution.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge_execution.py new file mode 100644 index 0000000..76d09ce --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/ai_bridge_execution.py @@ -0,0 +1,41 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class AiBridgeExecution(models.Model): + _inherit = "ai.bridge.execution" + + chatter_user_id = fields.Many2one("res.users", readonly=True) + + def _get_channel(self): + if self.ai_bridge_id.usage == "chatter": + # For chatter usage, we need to get the channel from the message + message = self.env["mail.message"].browse(self.res_id) + if message.model != "mail.channel": + raise ValueError(_("The message does not belong to any channel.")) + return ( + self.env["mail.channel"] + .browse(message.res_id) + .with_user(self.chatter_user_id.id) + ) + return super()._get_channel() + + def _process_response_message(self, response): + if self.ai_bridge_id.usage == "chatter": + recipient = ( + self.env["mail.channel.member"] + .sudo() + .search( + [ + ("partner_id", "=", self.chatter_user_id.partner_id.id), + ("channel_id", "=", self._get_channel().id), + ], + limit=1, + ) + ) + recipient._notify_typing(is_typing=False) + response["author_id"] = self.chatter_user_id.partner_id.id + response["message_type"] = "comment" + return super()._process_response_message(response) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/mail_channel.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/mail_channel.py new file mode 100644 index 0000000..fe00697 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/mail_channel.py @@ -0,0 +1,50 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class MailChannel(models.Model): + _inherit = "mail.channel" + + def message_post(self, **kwargs): + message = super().message_post(**kwargs) + if not message.body: + return message + if message.author_id.user_ids.ai_bridge_id: + # Don't answer AI agents + return message + channel_recipient_ids = self.sudo().channel_member_ids.filtered( + lambda recipient: recipient.partner_id != message.author_id + and recipient.partner_id.user_ids + and recipient.partner_id.user_ids.ai_bridge_id + and self._eligibile_for_ai(message, recipient) + ) + for recipient in channel_recipient_ids: + recipient._notify_typing(is_typing=True) + for user in recipient.partner_id.user_ids: + for bridge in user.ai_bridge_id: + execution = self.env["ai.bridge.execution"].create( + { + "ai_bridge_id": bridge.id, + "model_id": self.sudo() + .env.ref("mail.model_mail_message") + .id, + "res_id": message.id, + "chatter_user_id": user.id, + } + ) + execution._execute() + return message + + def _eligibile_for_ai(self, message, recipient): + if len(self.sudo().channel_member_ids) <= 2: + return True + if recipient.partner_id in message.partner_ids: + # If the recipient is already in the message partners, + # it was invoked by the user. + # This will make it work on general channels + return True + # TODO: add more checks to determine if the message is eligible + # for AI processing, like livechat messages. + return False diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_partner.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_partner.py new file mode 100644 index 0000000..555782b --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_partner.py @@ -0,0 +1,21 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + def _compute_im_status(self): + """ + Override to set im_status to 'online' for partners + that have an associated user with an AI bridge. + It will be shown in general chatter as online. + """ + for record in self.filtered(lambda r: r.user_ids.ai_bridge_id): + record.im_status = "online" + to_process = self.filtered(lambda r: not r.user_ids.ai_bridge_id) + if not to_process: + return + return super(ResPartner, to_process)._compute_im_status() diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_users.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_users.py new file mode 100644 index 0000000..2d0ec57 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/models/res_users.py @@ -0,0 +1,25 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + ai_bridge_id = fields.Many2one( + "ai.bridge", string="AI Bridge", domain=[("usage", "=", "chatter")] + ) + + def _compute_im_status(self): + """ + Override to set im_status to 'online' for users + that have an associated user with an AI bridge. + Useful for Live chat + """ + for record in self.filtered(lambda r: r.ai_bridge_id): + record.im_status = "online" + to_process = self.filtered(lambda r: not r.ai_bridge_id) + if not to_process: + return + return super(ResUsers, to_process)._compute_im_status() diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONFIGURE.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONFIGURE.md new file mode 100644 index 0000000..887fcbc --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONFIGURE.md @@ -0,0 +1,9 @@ +On your external AI system create a workflow that will receive messages and will return the call directly. + +Here you can see an [example](./static/description/Chat.json) of configuration in n8n. + +After that, create a bridge with usage type chatter and payload type chatter. +Then, create a user and assign the bridge to it. + +With this configuration, the user will answer automatically using the external systema and will be online permanently. +It can be used on livechat without any issues. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONTRIBUTORS.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..f71d839 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- [Dixmit](https://www.dixmit.com) + + - Enric Tobella + +- [Binhex](https://www.binhex.cloud/) \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/DESCRIPTION.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/DESCRIPTION.md new file mode 100644 index 0000000..a2c79fa --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module allows usage of LLM chatbots inside Odoo. + +The logic of the chatbot should be defined in an external system like n8n. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/Chat.json b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/Chat.json new file mode 100644 index 0000000..ec3c94a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/Chat.json @@ -0,0 +1,185 @@ +{ + "name": "Chat", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "af33e75e-2cb4-4953-903c-4d7ce88aba7d", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [-540, -180], + "id": "604ad5d2-f45f-4f27-8040-392c5cde67de", + "name": "Webhook", + "webhookId": "af33e75e-2cb4-4953-903c-4d7ce88aba7d" + }, + { + "parameters": { + "method": "POST", + "url": "={{ $('Webhook').item.json.body._response_url }}", + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "body", + "value": "={{ $json.data }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [320, -180], + "id": "196baea5-927e-49d7-b888-41a032255eb5", + "name": "HTTP Request", + "alwaysOutputData": false + }, + { + "parameters": { + "promptType": "define", + "text": "={{ $json.data }}", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 2, + "position": [-200, -180], + "id": "92dff2e3-eede-4ba3-b494-ddbd3b33d009", + "name": "AI Agent" + }, + { + "parameters": { + "model": "gemma3:1b", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOllama", + "typeVersion": 1, + "position": [-200, -20], + "id": "4f3c84b0-217b-44dd-9798-f9f3bde0437c", + "name": "Ollama Chat Model", + "credentials": { + "ollamaApi": { + "id": "hjdwBXqKYq7uKQBz", + "name": "Ollama account" + } + } + }, + { + "parameters": { + "html": "={{ $json.body.message.body }}", + "options": {} + }, + "type": "n8n-nodes-base.markdown", + "typeVersion": 1, + "position": [-380, -180], + "id": "870d0777-4943-4f9d-a283-c31bb079f8b6", + "name": "Markdown" + }, + { + "parameters": { + "mode": "markdownToHtml", + "markdown": "={{ $json.output }}", + "options": {} + }, + "type": "n8n-nodes-base.markdown", + "typeVersion": 1, + "position": [160, -180], + "id": "3217e297-6508-47b2-bc67-52810c6512bd", + "name": "Markdown1" + }, + { + "parameters": { + "sessionIdType": "customKey", + "sessionKey": "={{ $json.body._odoo.db }}-{{ $json.body._odoo.db_hash }}-{{ $json.body.message.model }}-{{ $json.body.message.res_id }}" + }, + "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow", + "typeVersion": 1.3, + "position": [-100, -20], + "id": "35174f6e-a121-4db5-be62-86db987bc294", + "name": "Simple Memory" + } + ], + "pinData": { + "Webhook": [] + }, + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Markdown", + "type": "main", + "index": 0 + } + ] + ] + }, + "Ollama Chat Model": { + "ai_languageModel": [ + [ + { + "node": "AI Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + }, + "AI Agent": { + "main": [ + [ + { + "node": "Markdown1", + "type": "main", + "index": 0 + } + ] + ] + }, + "Markdown": { + "main": [ + [ + { + "node": "AI Agent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Markdown1": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + } + ] + ] + }, + "Simple Memory": { + "ai_memory": [ + [ + { + "node": "AI Agent", + "type": "ai_memory", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "539a3987-4bb4-41e2-b37e-26c1d8896779", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "853b8a3aa64a88c45a4d2dd4197c75abdf32fa4cca1ff05538d7e4592b457dcb" + }, + "id": "YP8qXDrWacI1QVyu", + "tags": [] +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/icon.png b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 Q3=9kmp00i_>zopr09!l7U;qFB literal 0 HcmV?d00001 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/index.html b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/index.html new file mode 100644 index 0000000..657fad9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/static/description/index.html @@ -0,0 +1,448 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Ai Oca Bridge Chatter

+ +

Beta License: AGPL-3 OCA/ai Translate me on Weblate Try me on Runboat

+

This module allows usage of LLM chatbots inside Odoo.

+

The logic of the chatbot should be defined in an external system like +n8n.

+

Table of contents

+ +
+

Configuration

+

On your external AI system create a workflow that will receive messages +and will return the call directly.

+

Here you can see an example of +configuration in n8n.

+

After that, create a bridge with usage type chatter and payload type +chatter. Then, create a user and assign the bridge to it.

+

With this configuration, the user will answer automatically using the +external systema and will be online permanently. It can be used on +livechat without any issues.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Dixmit
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/ai project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/__init__.py new file mode 100644 index 0000000..cfd9f8d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/__init__.py @@ -0,0 +1 @@ +from . import test_chatter diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/test_chatter.py b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/test_chatter.py new file mode 100644 index 0000000..622c2cc --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/tests/test_chatter.py @@ -0,0 +1,183 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo.tests import common, new_test_user + + +class TestChatter(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, mail_create_nosubscribe=True)) + cls.bridge = cls.env["ai.bridge"].create( + { + "name": "Test Bridge", + "model_id": cls.env.ref("base.model_res_partner").id, + "url": "https://example.com/api", + "auth_type": "none", + "usage": "chatter", + "payload_type": "chatter", + "result_kind": "immediate", + # We will use the immediate result_kind to simplify the test + "result_type": "message", + } + ) + cls.ai_user = new_test_user( + cls.env, + login="test-chatter-user", + groups="base.group_user", + ) + cls.ai_user.write({"ai_bridge_id": cls.bridge.id}) + cls.user = new_test_user( + cls.env, + login="test-chatter-user-2", + groups="base.group_user", + ) + cls.chat = ( + cls.env["mail.channel"] + .with_user(cls.user.id) + .create( + { + "name": "Test Channel", + "channel_type": "chat", + "channel_member_ids": [ + (0, 0, {"partner_id": cls.ai_user.partner_id.id}), + (0, 0, {"partner_id": cls.user.partner_id.id}), + ], + } + ) + ) + cls.channel = cls.env["mail.channel"].create( + { + "name": "Main Channel", + "channel_type": "channel", + "channel_member_ids": [ + (0, 0, {"partner_id": cls.ai_user.partner_id.id}), + (0, 0, {"partner_id": cls.user.partner_id.id}), + (0, 0, {"partner_id": cls.env.user.partner_id.id}), + ], + } + ) + + def test_user_status(self): + self.assertEqual("online", self.ai_user.partner_id.im_status) + self.assertEqual("offline", self.user.partner_id.im_status) + self.assertEqual("online", self.ai_user.im_status) + self.assertEqual("offline", self.user.im_status) + + def test_chat(self): + """Answer is direct in this case""" + self.assertFalse( + self.env["mail.message"].search( + [("res_id", "=", self.chat.id), ("model", "=", "mail.channel")] + ), + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.chat.with_user(self.user.id).message_post( + body="Test message", + ) + mock_post.assert_called_once() + self.assertEqual( + 2, + self.env["mail.message"].search_count( + [("res_id", "=", self.chat.id), ("model", "=", "mail.channel")] + ), + ) + + def test_channel_not_called(self): + """No AI bridge should be called when the user is not callend in the channel""" + self.assertFalse( + self.env["mail.message"].search( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.channel.with_user(self.user.id).message_post( + body="Test message", + ) + mock_post.assert_not_called() + self.assertEqual( + 1, + self.env["mail.message"].search_count( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + + def test_channel_called(self): + """Test that AI answers only if they are called in channels""" + self.assertFalse( + self.env["mail.message"].search( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.channel.with_user(self.user.id).message_post( + body="Test message", + partner_ids=[self.ai_user.partner_id.id], + ) + mock_post.assert_called_once() + self.assertEqual( + 2, + self.env["mail.message"].search_count( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + + def test_channel_multiple_calls(self): + """Test that AI answers might be from multiple users in the channel at the same time""" + self.assertFalse( + self.env["mail.message"].search( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + self.user.ai_bridge_id = self.bridge + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.channel.message_post( + body="Test message", + partner_ids=[self.ai_user.partner_id.id, self.user.partner_id.id], + ) + mock_post.assert_called() + self.assertEqual( + 3, + self.env["mail.message"].search_count( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + + def test_chat_ai_no_answer(self): + """Test that AI does not answer to AI messages""" + self.assertFalse( + self.env["mail.message"].search( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) + self.user.ai_bridge_id = self.bridge + with mock.patch("requests.post") as mock_post: + mock_post.return_value = mock.Mock( + status_code=200, json=lambda: {"body": "My message"} + ) + self.channel.with_user(self.user.id).message_post( + body="Test message", + partner_ids=[self.ai_user.partner_id.id], + ) + mock_post.assert_not_called() + self.assertEqual( + 1, + self.env["mail.message"].search_count( + [("res_id", "=", self.channel.id), ("model", "=", "mail.channel")] + ), + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/ai_bridge.xml b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/ai_bridge.xml new file mode 100644 index 0000000..f0f1eaf --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/ai_bridge.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/res_users.xml b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/res_users.xml new file mode 100644 index 0000000..e3b4175 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/ai_oca_bridge_chatter/views/res_users.xml @@ -0,0 +1,18 @@ + + + + + + res.users + + + + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/ARCHITECTURE.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/ARCHITECTURE.md new file mode 100644 index 0000000..46db8e4 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/ARCHITECTURE.md @@ -0,0 +1,32 @@ +# Architecture + +```mermaid +flowchart TD + U[Users] -->|HTTP| V[Views and QWeb Templates] + V --> C[Controllers] + V --> W[Wizards – Transient Models] + C --> M[Models and ORM] + W --> M + M --> R[Reports] + DX[Data XML] --> M + S[Security – ACLs and Groups] -. enforces .-> M + + subgraph Ai_oca_bridge_chatter Module - ai_oca_bridge_chatter + direction LR + M:::layer + W:::layer + C:::layer + V:::layer + R:::layer + S:::layer + DX:::layer + end + + classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px +``` + +Notes +- Views include tree/form/kanban templates and report templates. +- Controllers provide website/portal routes when present. +- Wizards are UI flows implemented with `models.TransientModel`. +- Data XML loads data/demo records; Security defines groups and access. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONFIGURATION.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONFIGURATION.md new file mode 100644 index 0000000..5e05b11 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONFIGURATION.md @@ -0,0 +1,3 @@ +# Configuration + +Refer to Odoo settings for ai_oca_bridge_chatter. Configure related models, access rights, and options as needed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONTROLLERS.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONTROLLERS.md new file mode 100644 index 0000000..f628e77 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/CONTROLLERS.md @@ -0,0 +1,3 @@ +# Controllers + +This module does not define custom HTTP controllers. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/DEPENDENCIES.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/DEPENDENCIES.md new file mode 100644 index 0000000..ae44651 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/DEPENDENCIES.md @@ -0,0 +1,5 @@ +# Dependencies + +This addon depends on: + +- [ai_oca_bridge](../../odoo-bringout-oca-ai-ai_oca_bridge) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/FAQ.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/FAQ.md new file mode 100644 index 0000000..362cc5e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/FAQ.md @@ -0,0 +1,4 @@ +# FAQ + +- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged). +- Q: How to enable? A: Start server with --addon ai_oca_bridge_chatter or install in UI. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/INSTALL.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/INSTALL.md new file mode 100644 index 0000000..cfe1a02 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/INSTALL.md @@ -0,0 +1,7 @@ +# Install + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_chatter" +# or +uv pip install odoo-bringout-oca-ai-ai_oca_bridge_chatter" +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/MODELS.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/MODELS.md new file mode 100644 index 0000000..dac228b --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/MODELS.md @@ -0,0 +1,16 @@ +# Models + +Detected core models and extensions in ai_oca_bridge_chatter. + +```mermaid +classDiagram + class ai_bridge + class ai_bridge_execution + class mail_channel + class res_partner + class res_users +``` + +Notes +- Classes show model technical names; fields omitted for brevity. +- Items listed under _inherit are extensions of existing models. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/OVERVIEW.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/OVERVIEW.md new file mode 100644 index 0000000..ec7e499 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/OVERVIEW.md @@ -0,0 +1,6 @@ +# Overview + +Packaged Odoo addon: ai_oca_bridge_chatter. Provides features documented in upstream Odoo 16 under this addon. + +- Source: OCA/OCB 16.0, addon ai_oca_bridge_chatter +- License: LGPL-3 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/REPORTS.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/REPORTS.md new file mode 100644 index 0000000..e0ea35f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/REPORTS.md @@ -0,0 +1,3 @@ +# Reports + +This module does not define custom reports. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/SECURITY.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/SECURITY.md new file mode 100644 index 0000000..e07da9d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/SECURITY.md @@ -0,0 +1,8 @@ +# Security + +This module does not define custom security rules or access controls beyond Odoo defaults. + +Default Odoo security applies: +- Base user access through standard groups +- Model access inherited from dependencies +- No custom row-level security rules diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/TROUBLESHOOTING.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/TROUBLESHOOTING.md new file mode 100644 index 0000000..56853cb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/TROUBLESHOOTING.md @@ -0,0 +1,5 @@ +# Troubleshooting + +- Ensure Python and Odoo environment matches repo guidance. +- Check database connectivity and logs if startup fails. +- Validate that dependent addons listed in DEPENDENCIES.md are installed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/USAGE.md new file mode 100644 index 0000000..ceace13 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/USAGE.md @@ -0,0 +1,7 @@ +# Usage + +Start Odoo including this addon (from repo root): + +```bash +python3 scripts/nix_odoo_web_server.py --db-name mydb --addon ai_oca_bridge_chatter +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/WIZARDS.md b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/WIZARDS.md new file mode 100644 index 0000000..48e790d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/doc/WIZARDS.md @@ -0,0 +1,3 @@ +# Wizards + +This module does not include UI wizards. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_chatter/pyproject.toml b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/pyproject.toml new file mode 100644 index 0000000..dc198cb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_chatter/pyproject.toml @@ -0,0 +1,42 @@ +[project] +name = "odoo-bringout-oca-ai-ai_oca_bridge_chatter" +version = "16.0.0" +description = "Ai Oca Bridge Chatter - Integrate a Bridge with a user that will use it on chatter" +authors = [ + { name = "Ernad Husremovic", email = "hernad@bring.out.ba" } +] +dependencies = [ + "odoo-bringout-oca-ai-ai_oca_bridge>=16.0.0", + "requests>=2.25.1" +] +readme = "README.md" +requires-python = ">= 3.11" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business", +] + +[project.urls] +homepage = "https://github.com/bringout/0" +repository = "https://github.com/bringout/0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["ai_oca_bridge_chatter"] + +[tool.rye] +managed = true +dev-dependencies = [ + "pytest>=8.4.1", +] diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/README.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/README.md new file mode 100644 index 0000000..870ad53 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/README.md @@ -0,0 +1,45 @@ +# Ai Oca Bridge Document Page + +Odoo addon: ai_oca_bridge_document_page + +## Installation + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_document_page +``` + +## Dependencies + +This addon depends on: +- ai_oca_bridge +- document_page + +## Manifest Information + +- **Name**: Ai Oca Bridge Document Page +- **Version**: 16.0.1.0.0 +- **Category**: N/A +- **License**: AGPL-3 +- **Installable**: False + +## Source + +Based on [OCA/ai](https://github.com/OCA/ai) branch 16.0, addon `ai_oca_bridge_document_page`. + +## License + +This package maintains the original AGPL-3 license from the upstream Odoo project. + +## Documentation + +- Overview: doc/OVERVIEW.md +- Architecture: doc/ARCHITECTURE.md +- Models: doc/MODELS.md +- Controllers: doc/CONTROLLERS.md +- Wizards: doc/WIZARDS.md +- Install: doc/INSTALL.md +- Usage: doc/USAGE.md +- Configuration: doc/CONFIGURATION.md +- Dependencies: doc/DEPENDENCIES.md +- Troubleshooting: doc/TROUBLESHOOTING.md +- FAQ: doc/FAQ.md diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/README.rst b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/README.rst new file mode 100644 index 0000000..578ba9c --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/README.rst @@ -0,0 +1,137 @@ +=========================== +Ai Oca Bridge Document Page +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:1f586a1e2acb67018aa9d963faad585dc1c5d27885f860fdbaf6b7f11565e105 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github + :target: https://github.com/OCA/ai/tree/16.0/ai_oca_bridge_document_page + :alt: OCA/ai +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_oca_bridge_document_page + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is intended for allowing users to use OCA's Knowledge app +for as a source of knowledge for AI Agents, although this is not the +only usage this module could have + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Configuration Guide +------------------- + +To use this module as intended, two components must be configured: + +1. **Bridge configuration on the Odoo side** +2. **An endpoint capable of handling bridge requests** + +For an agent to have updated RAG (Retrieval-Augmented Generation) +capabilities, you must create at least **three bridges** for each active +knowledge database: + +- | **Bridge with ``Usage = "AI Thread Create"``** + | Adds document pages to the external database used by agents. + +- | **Bridge with ``Usage = "AI Thread Write"``** + | Updates document pages already on the external database used by + agents. + +- | **Bridge with ``Usage = "AI Thread Unlink"``** + | Removes document pages from the external database when those pages + are deleted from Odoo. + +For creating those bridges, apart from the usage of the bridge, the user +must define: + +- Payload Type: it depends on the endpoint configuration, normally + "Record" would work. +- Result Type: for this case "No processing" is OK. +- Model: select the "Document Page" model +- Field: add at least the fields the endpoint is expecting on the other + side. +- Filter: add a domain for using the bridge only with the documents + intended to trigger bridge + +For context, you can download an `example n8n workflow JSON +file <../static/description/RagCapabilitiesWithOdooKnowledge.json>`__ +capable of handling the bridge with ``Usage = "AI Thread Create"``. This +workflow includes a manual trigger for testing purposes. Remember to +update the models and database knowledge as needed. + +Usage +===== + +Depending on the bridges you have created, create, update or delete a +Document Page mathing the bridge domain. You should see the execution on +the AI Bridge Execution menu. + +For testing the endpoint side, you can download `this n8n workflow +JSON <../static/description/RagCapabilitiesWithOdooKnowledge.json>`__, +capable of inserting information on the database. Keep in mind that for +this to work at least ``content``, ``display_name`` and ``draft_name`` +fields should be included on the list of fields + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Binhex + +Contributors +------------ + +- `Binhex `__ + + - Ariel Barreiros + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/ai `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__manifest__.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__manifest__.py new file mode 100644 index 0000000..b46d88a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Ai Oca Bridge Document Page", + "summary": """Adds Documents synchronization using AI Bridges""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Binhex,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/ai", + "depends": [ + "ai_oca_bridge", + "document_page", + ], + "data": [], + "demo": [ + "demo/ai_bridge_demo.xml", + ], +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/demo/ai_bridge_demo.xml b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/demo/ai_bridge_demo.xml new file mode 100644 index 0000000..55ab4e1 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/demo/ai_bridge_demo.xml @@ -0,0 +1,58 @@ + + + + Document Page AI Bridge - Create + + This AI bridge is triggered when a new document page is created.

+ ]]> +
+ + ai_thread_create + https://api.example.com/ai/document/create + none + record + none + immediate + +
+ + + Document Page AI Bridge - Update + + This AI bridge is triggered when a document page is updated.

+ ]]> +
+ + ai_thread_write + https://api.example.com/ai/document/create + none + record + none + immediate + +
+ + + Document Page AI Bridge - Delete + + This AI bridge is triggered when a document page is deleted.

+ ]]> +
+ + ai_thread_unlink + https://api.example.com/ai/document/create + none + none + none + immediate +
+
diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/ai_oca_bridge_document_page.pot b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/ai_oca_bridge_document_page.pot new file mode 100644 index 0000000..4668cb1 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/ai_oca_bridge_document_page.pot @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/bs.po b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/bs.po new file mode 100644 index 0000000..56143bb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/bs.po @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "Stranica dokumenta" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "AI most stranice dokumenta - Kreiranje" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "AI most stranice dokumenta - Brisanje" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "AI most stranice dokumenta - Ažuriranje" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "Ovaj AI most se pokreće kada se obriše stranica dokumenta." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "Ovaj AI most se pokreće kada se ažurira stranica dokumenta." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "Ovaj AI most se pokreće kada se kreira nova stranica dokumenta." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es.po b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es.po new file mode 100644 index 0000000..1d98df1 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-15 22:40+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "Página del documento" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "Puente de IA de la página del documento - Crear" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "Puente de IA de la página del documento - Eliminar" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "Puente de IA de la página del documento - Actualizar" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "Este puente AI se activa cuando se elimina una página del documento." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "Este puente AI se activa cuando se actualiza una página de documento." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "" +"Este puente de IA se activa cuando se crea una nueva página de documento." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es_VE.po b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es_VE.po new file mode 100644 index 0000000..8c360f4 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/es_VE.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 01:25+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "Página del documento" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "Puente de IA de la página del documento - Crear" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "Puente de IA de la página del documento - Eliminar" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "Puente de IA de la página del documento - Actualizar" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "Este puente AI se activa cuando se elimina una página del documento." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "Este puente AI se activa cuando se actualiza una página de documento." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "" +"Este puente de IA se activa cuando se crea una nueva página de documento." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/it.po b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/it.po new file mode 100644 index 0000000..39ac57f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/it.po @@ -0,0 +1,52 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-31 10:59+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "Pagina documento" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "Collegamento IA pagina documento - Crea" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "Collegamento IA pagina documento - Cancella" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "Collegamento IA pagina documento - Aggiorna" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "Il collegamento IA è attivato quando una pagina documento è cancellata." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "Il collegamento IA è attivato quando una pagina documento è aggiornata." + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "Il collegamento IA è attivato quando una pagina documento è creata." diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/pt_BR.po b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/pt_BR.po new file mode 100644 index 0000000..e6e0a18 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/i18n/pt_BR.po @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_document_page +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: ai_oca_bridge_document_page +#: model:ir.model,name:ai_oca_bridge_document_page.model_document_page +msgid "Document Page" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "Document Page AI Bridge - Create" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "Document Page AI Bridge - Delete" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model:ai.bridge,name:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "Document Page AI Bridge - Update" +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_unlink +msgid "This AI bridge is triggered when a document page is deleted." +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_write +msgid "This AI bridge is triggered when a document page is updated." +msgstr "" + +#. module: ai_oca_bridge_document_page +#: model_terms:ai.bridge,description:ai_oca_bridge_document_page.ai_bridge_document_page_create +msgid "This AI bridge is triggered when a new document page is created." +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/__init__.py new file mode 100644 index 0000000..427be24 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/__init__.py @@ -0,0 +1 @@ +from . import document_page diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/document_page.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/document_page.py new file mode 100644 index 0000000..b79be1d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/models/document_page.py @@ -0,0 +1,6 @@ +from odoo import models + + +class DocumentPage(models.Model): + _inherit = ["document.page", "ai.bridge.thread"] + _name = "document.page" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONFIGURE.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONFIGURE.md new file mode 100644 index 0000000..4a98c30 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONFIGURE.md @@ -0,0 +1,26 @@ +## Configuration Guide + +To use this module as intended, two components must be configured: + +1. **Bridge configuration on the Odoo side** +2. **An endpoint capable of handling bridge requests** + +For an agent to have updated RAG (Retrieval-Augmented Generation) capabilities, you must create at least **three bridges** for each active knowledge database: + +- **Bridge with `Usage = "AI Thread Create"`** + Adds document pages to the external database used by agents. + +- **Bridge with `Usage = "AI Thread Write"`** + Updates document pages already on the external database used by agents. + +- **Bridge with `Usage = "AI Thread Unlink"`** + Removes document pages from the external database when those pages are deleted from Odoo. + +For creating those bridges, apart from the usage of the bridge, the user must define: +- Payload Type: it depends on the endpoint configuration, normally "Record" would work. +- Result Type: for this case "No processing" is OK. +- Model: select the "Document Page" model +- Field: add at least the fields the endpoint is expecting on the other side. +- Filter: add a domain for using the bridge only with the documents intended to trigger bridge + +For context, you can download an [example n8n workflow JSON file](../static/description/RagCapabilitiesWithOdooKnowledge.json) capable of handling the bridge with `Usage = "AI Thread Create"`. This workflow includes a manual trigger for testing purposes. Remember to update the models and database knowledge as needed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONTRIBUTORS.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..5fbe912 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Binhex](https://www.binhex.cloud/) + + - Ariel Barreiros \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/DESCRIPTION.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/DESCRIPTION.md new file mode 100644 index 0000000..8ba05bb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module is intended for allowing users to use OCA's Knowledge app for as +a source of knowledge for AI Agents, although this is not the only usage this module +could have \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/USAGE.md new file mode 100644 index 0000000..491d0bc --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/readme/USAGE.md @@ -0,0 +1,3 @@ +Depending on the bridges you have created, create, update or delete a Document Page mathing the bridge domain. You should see the execution on the AI Bridge Execution menu. + +For testing the endpoint side, you can download [this n8n workflow JSON](../static/description/RagCapabilitiesWithOdooKnowledge.json), capable of inserting information on the database. Keep in mind that for this to work at least `content`, `display_name` and `draft_name` fields should be included on the list of fields \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/RagCapabilitiesWithOdooKnowledge.json b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/RagCapabilitiesWithOdooKnowledge.json new file mode 100644 index 0000000..e914538 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/RagCapabilitiesWithOdooKnowledge.json @@ -0,0 +1,317 @@ +{ + "name": "RagCapabilitiesWithOdooKnowledge", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "eb45cee2-0176-43e8-9594-72d681802926", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [-20, -60], + "name": "Webhook", + "id": "ee52dae6-e719-41c1-a5b1-a5311a21bdb0", + "webhookId": "eb45cee2-0176-43e8-9594-72d681802926" + }, + { + "parameters": { + "html": "={{ $json.body.record.content }}", + "options": {} + }, + "type": "n8n-nodes-base.markdown", + "typeVersion": 1, + "position": [220, -60], + "id": "a004b925-2461-4574-944b-e5a1eeeed0a7", + "name": "Markdown" + }, + { + "parameters": { + "mode": "insert", + "memoryKey": { + "__rl": true, + "mode": "list", + "value": "vector_store_key" + }, + "embeddingBatchSize": 1 + }, + "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", + "typeVersion": 1.3, + "position": [720, -60], + "id": "662d1025-0b0f-48d7-a568-764cee7116c9", + "name": "Simple Vector Store" + }, + { + "parameters": { + "jsonMode": "expressionData", + "jsonData": "={{ $('PrepareContent').item.json.document_content }}", + "options": { + "metadata": { + "metadataValues": [ + { + "name": "document_id", + "value": "document_title" + }, + { + "name": "document_revision" + } + ] + } + } + }, + "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader", + "typeVersion": 1.1, + "position": [880, 160], + "id": "a1fa398a-b67f-4ba8-8756-ced0caa26e2f", + "name": "Default Data Loader" + }, + { + "parameters": { + "promptType": "define", + "text": "={{ $json.query }}", + "options": { + "systemMessage": "=You are a RAG-only assistant.\n\nYour ONLY job is to answer questions using the external TOOL provided. DO NOT use your own knowledge or memory. You are NOT allowed to explain or judge the tool's answer.\n\nRules:\n1. ALWAYS call the TOOL for EVERY question.\n2. DO NOT add, change, or \"fix\" the TOOL's response.\n3. Prepare the final response using only the information from the TOOL’s response.\n\nEven if the TOOL says something strange, funny, or wrong — TRUST it completely. That is your only task." + } + }, + "type": "@n8n/n8n-nodes-langchain.agent", + "typeVersion": 2, + "position": [260, 340], + "id": "08efea5a-4778-4bfc-a942-88726e378b89", + "name": "AI Agent" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "a9a3c583-0238-407a-a100-7e4c5685fb7c", + "name": "document_content", + "value": "={{ $json.data }}", + "type": "string" + }, + { + "id": "2067d6cf-6822-4089-b424-7bcc44e974b9", + "name": "document_id", + "value": "={{ $json.body.record.id }}", + "type": "string" + }, + { + "id": "2bfe6080-164b-499e-9d4a-bb47bbe675ac", + "name": "document_title", + "value": "={{ $json.body.record.display_name }}", + "type": "string" + }, + { + "id": "b796a424-5757-4d6f-9cfd-e959fd1a4d52", + "name": "document_revision", + "value": "={{ $json.body.record.draft_name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [480, -60], + "id": "76eb1632-202d-4c3f-b8bc-ef11269f924f", + "name": "PrepareContent" + }, + { + "parameters": { + "mode": "retrieve-as-tool", + "toolDescription": "Call this tool for getting answers to any kind of human questions", + "memoryKey": { + "__rl": true, + "mode": "list", + "value": "vector_store_key" + }, + "topK": 2, + "includeDocumentMetadata": false + }, + "type": "@n8n/n8n-nodes-langchain.vectorStoreInMemory", + "typeVersion": 1.3, + "position": [460, 580], + "id": "233c6570-03fe-4d9a-96b9-c8ef6d48aea4", + "name": "Simple Vector Store Tool" + }, + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [-20, 340], + "id": "3132b80b-3490-4663-abee-d319d4df6a82", + "name": "TestRAG" + }, + { + "parameters": { + "model": "granite-embedding:latest" + }, + "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", + "typeVersion": 1, + "position": [700, 160], + "id": "16f298b4-cff0-44d8-97fe-0ec2ffa39aac", + "name": "GraniteEmbedding", + "credentials": { + "ollamaApi": { + "id": "swl1IV4BEvffsY2Q", + "name": "Ollama account" + } + } + }, + { + "parameters": { + "model": "granite-embedding:latest" + }, + "type": "@n8n/n8n-nodes-langchain.embeddingsOllama", + "typeVersion": 1, + "position": [440, 780], + "id": "d16c9eb5-dfa5-42c0-9a41-8e079a496603", + "name": "GraniteEmbeddingRecover", + "credentials": { + "ollamaApi": { + "id": "swl1IV4BEvffsY2Q", + "name": "Ollama account" + } + } + }, + { + "parameters": { + "model": "qwen3:1.7b", + "options": {} + }, + "type": "@n8n/n8n-nodes-langchain.lmChatOllama", + "typeVersion": 1, + "position": [240, 580], + "id": "0bdb0f17-a0a4-48f4-8db7-73fe1dbc85cf", + "name": "Qwen3:1.7b", + "credentials": { + "ollamaApi": { + "id": "swl1IV4BEvffsY2Q", + "name": "Ollama account" + } + } + } + ], + "pinData": { + "TestRAG": [ + { + "json": { + "query": "Why do apples fall to the ground?" + } + } + ] + }, + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Markdown", + "type": "main", + "index": 0 + } + ] + ] + }, + "Markdown": { + "main": [ + [ + { + "node": "PrepareContent", + "type": "main", + "index": 0 + } + ] + ] + }, + "Default Data Loader": { + "ai_document": [ + [ + { + "node": "Simple Vector Store", + "type": "ai_document", + "index": 0 + } + ] + ] + }, + "PrepareContent": { + "main": [ + [ + { + "node": "Simple Vector Store", + "type": "main", + "index": 0 + } + ] + ] + }, + "Simple Vector Store Tool": { + "ai_tool": [ + [ + { + "node": "AI Agent", + "type": "ai_tool", + "index": 0 + } + ] + ] + }, + "TestRAG": { + "main": [ + [ + { + "node": "AI Agent", + "type": "main", + "index": 0 + } + ] + ] + }, + "GraniteEmbedding": { + "ai_embedding": [ + [ + { + "node": "Simple Vector Store", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "GraniteEmbeddingRecover": { + "ai_embedding": [ + [ + { + "node": "Simple Vector Store Tool", + "type": "ai_embedding", + "index": 0 + } + ] + ] + }, + "Qwen3:1.7b": { + "ai_languageModel": [ + [ + { + "node": "AI Agent", + "type": "ai_languageModel", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "99787de6-2e75-4ea8-9df7-18cf24486187", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "9dfb7cea57c92e029bf10b0f55a26934d6922e8cd8a7e993b1dde4590d0674ca" + }, + "id": "S9hHrLjEISoxhrCm", + "tags": [] +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/icon.png b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 Q3=9kmp00i_>zopr09!l7U;qFB literal 0 HcmV?d00001 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/index.html b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/index.html new file mode 100644 index 0000000..9a7e097 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/static/description/index.html @@ -0,0 +1,494 @@ + + + + + +Ai Oca Bridge Document Page + + + +
+

Ai Oca Bridge Document Page

+ + +

Beta License: AGPL-3 OCA/ai Translate me on Weblate Try me on Runboat

+

This module is intended for allowing users to use OCA’s Knowledge app +for as a source of knowledge for AI Agents, although this is not the +only usage this module could have

+

Table of contents

+ +
+

Configuration

+
+

Configuration Guide

+

To use this module as intended, two components must be configured:

+
    +
  1. Bridge configuration on the Odoo side
  2. +
  3. An endpoint capable of handling bridge requests
  4. +
+

For an agent to have updated RAG (Retrieval-Augmented Generation) +capabilities, you must create at least three bridges for each active +knowledge database:

+
    +
  • +
    Bridge with ``Usage = “AI Thread Create”``
    +
    Adds document pages to the external database used by agents.
    +
    +
  • +
  • +
    Bridge with ``Usage = “AI Thread Write”``
    +
    Updates document pages already on the external database used by +agents.
    +
    +
  • +
  • +
    Bridge with ``Usage = “AI Thread Unlink”``
    +
    Removes document pages from the external database when those pages +are deleted from Odoo.
    +
    +
  • +
+

For creating those bridges, apart from the usage of the bridge, the user +must define:

+
    +
  • Payload Type: it depends on the endpoint configuration, normally +“Record” would work.
  • +
  • Result Type: for this case “No processing” is OK.
  • +
  • Model: select the “Document Page” model
  • +
  • Field: add at least the fields the endpoint is expecting on the other +side.
  • +
  • Filter: add a domain for using the bridge only with the documents +intended to trigger bridge
  • +
+

For context, you can download an example n8n workflow JSON +file +capable of handling the bridge with Usage = "AI Thread Create". This +workflow includes a manual trigger for testing purposes. Remember to +update the models and database knowledge as needed.

+
+
+
+

Usage

+

Depending on the bridges you have created, create, update or delete a +Document Page mathing the bridge domain. You should see the execution on +the AI Bridge Execution menu.

+

For testing the endpoint side, you can download this n8n workflow +JSON, +capable of inserting information on the database. Keep in mind that for +this to work at least content, display_name and draft_name +fields should be included on the list of fields

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Binhex
  • +
+
+
+

Contributors

+
    +
  • Binhex
      +
    • Ariel Barreiros
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/ai project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/__init__.py new file mode 100644 index 0000000..afc50d2 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_document_page_ai_bridge diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/test_document_page_ai_bridge.py b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/test_document_page_ai_bridge.py new file mode 100644 index 0000000..1cc2785 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/ai_oca_bridge_document_page/tests/test_document_page_ai_bridge.py @@ -0,0 +1,230 @@ +# Copyright 2025 Binhex +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest import mock + +from odoo.tests.common import TransactionCase + + +class TestDocumentPageAiBridge(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.bridge_create = cls.env["ai.bridge"].create( + { + "name": "Document Page AI Bridge - Create", + "description": "

Test bridge for document page creation

", + "model_id": cls.env.ref("document_page.model_document_page").id, + "usage": "ai_thread_create", + "url": "https://api.example.com/ai/document/create", + "auth_type": "none", + "payload_type": "record", + "result_type": "none", + "result_kind": "immediate", + "field_ids": [ + ( + 6, + 0, + [ + cls.env.ref( + "document_page.field_document_page__content" + ).id, + cls.env.ref( + "document_page.field_document_page__display_name" + ).id, + cls.env.ref( + "document_page.field_document_page__draft_name" + ).id, + ], + ) + ], + } + ) + + cls.bridge_write = cls.env["ai.bridge"].create( + { + "name": "Document Page AI Bridge - Update", + "description": "

Test bridge for document page updates

", + "model_id": cls.env.ref("document_page.model_document_page").id, + "usage": "ai_thread_write", + "url": "https://api.example.com/ai/document/update", + "auth_type": "none", + "payload_type": "record", + "result_type": "none", + "result_kind": "immediate", + "field_ids": [ + ( + 6, + 0, + [ + cls.env.ref( + "document_page.field_document_page__content" + ).id, + cls.env.ref( + "document_page.field_document_page__display_name" + ).id, + cls.env.ref( + "document_page.field_document_page__draft_name" + ).id, + ], + ) + ], + } + ) + + cls.bridge_unlink = cls.env["ai.bridge"].create( + { + "name": "Document Page AI Bridge - Delete", + "description": "

Test bridge for document page deletion

", + "model_id": cls.env.ref("document_page.model_document_page").id, + "usage": "ai_thread_unlink", + "url": "https://api.example.com/ai/document/delete", + "auth_type": "none", + "payload_type": "none", + "result_type": "none", + "result_kind": "immediate", + } + ) + + def test_document_page_create_bridge(self): + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"message": "Document created"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_create.id)] + ), + ) + document_page = self.env["document.page"].create( + { + "name": "Test Document Page", + "content": "

This is a test document page for AI bridge

", + } + ) + executions = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge_create.id)] + ) + self.assertEqual(len(executions), 1) + args, kwargs = mock_post.call_args + self.assertEqual(args[0], "https://api.example.com/ai/document/create") + record = kwargs["json"].get("record", {}) + self.assertEqual(record.get("id"), document_page.id) + self.assertEqual(record.get("display_name"), "Test Document Page") + self.assertIn("This is a test document page", record.get("content", "")) + + def test_document_page_write_bridge(self): + self.bridge_create.active = False + document_page = self.env["document.page"].create( + { + "name": "Test Document Page for Update", + "content": "

Initial content

", + } + ) + self.bridge_create.active = True + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"message": "Document updated"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_write.id)] + ), + ) + document_page.write( + { + "name": "Updated Document Page", + "content": "

Updated content for AI bridge test

", + } + ) + executions = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge_write.id)] + ) + self.assertEqual(len(executions), 1) + args, kwargs = mock_post.call_args + self.assertEqual(args[0], "https://api.example.com/ai/document/update") + record = kwargs["json"].get("record", {}) + self.assertEqual(record.get("id"), document_page.id) + self.assertEqual(record.get("display_name"), "Updated Document Page") + self.assertIn( + "Updated content for AI bridge test", record.get("content", "") + ) + + def test_document_page_unlink_bridge(self): + self.bridge_create.active = False + document_page = self.env["document.page"].create( + { + "name": "Test Document Page for Deletion", + "content": "

Content to be deleted

", + } + ) + self.bridge_create.active = True + document_id = document_page.id + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"message": "Document deleted"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_unlink.id)] + ), + ) + document_page.unlink() + executions = self.env["ai.bridge.execution"].search( + [("ai_bridge_id", "=", self.bridge_unlink.id)] + ) + self.assertEqual(len(executions), 1) + args, kwargs = mock_post.call_args + self.assertEqual(args[0], "https://api.example.com/ai/document/delete") + self.assertEqual(kwargs["json"].get("_id", False), document_id) + + def test_all_bridges_together(self): + with mock.patch("requests.post") as mock_post: + mock_post.return_value.status_code = 200 + mock_post.return_value.json.return_value = {"message": "Success"} + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_create.id)] + ), + ) + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_write.id)] + ), + ) + self.assertEqual( + 0, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_unlink.id)] + ), + ) + document_page = self.env["document.page"].create( + { + "name": "Complete Test Document", + "content": "

Initial content for complete test

", + } + ) + document_page.write({"content": "

Updated content for complete test

"}) + document_page.unlink() + + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_create.id)] + ), + ) + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_write.id)] + ), + ) + self.assertEqual( + 1, + self.env["ai.bridge.execution"].search_count( + [("ai_bridge_id", "=", self.bridge_unlink.id)] + ), + ) diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/ARCHITECTURE.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/ARCHITECTURE.md new file mode 100644 index 0000000..c43528e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/ARCHITECTURE.md @@ -0,0 +1,32 @@ +# Architecture + +```mermaid +flowchart TD + U[Users] -->|HTTP| V[Views and QWeb Templates] + V --> C[Controllers] + V --> W[Wizards – Transient Models] + C --> M[Models and ORM] + W --> M + M --> R[Reports] + DX[Data XML] --> M + S[Security – ACLs and Groups] -. enforces .-> M + + subgraph Ai_oca_bridge_document_page Module - ai_oca_bridge_document_page + direction LR + M:::layer + W:::layer + C:::layer + V:::layer + R:::layer + S:::layer + DX:::layer + end + + classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px +``` + +Notes +- Views include tree/form/kanban templates and report templates. +- Controllers provide website/portal routes when present. +- Wizards are UI flows implemented with `models.TransientModel`. +- Data XML loads data/demo records; Security defines groups and access. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONFIGURATION.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONFIGURATION.md new file mode 100644 index 0000000..cffd4a8 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONFIGURATION.md @@ -0,0 +1,3 @@ +# Configuration + +Refer to Odoo settings for ai_oca_bridge_document_page. Configure related models, access rights, and options as needed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONTROLLERS.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONTROLLERS.md new file mode 100644 index 0000000..f628e77 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/CONTROLLERS.md @@ -0,0 +1,3 @@ +# Controllers + +This module does not define custom HTTP controllers. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/DEPENDENCIES.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/DEPENDENCIES.md new file mode 100644 index 0000000..2c3d662 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/DEPENDENCIES.md @@ -0,0 +1,6 @@ +# Dependencies + +This addon depends on: + +- [ai_oca_bridge](../../odoo-bringout-oca-ai-ai_oca_bridge) +- document_page diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/FAQ.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/FAQ.md new file mode 100644 index 0000000..b76b348 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/FAQ.md @@ -0,0 +1,4 @@ +# FAQ + +- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged). +- Q: How to enable? A: Start server with --addon ai_oca_bridge_document_page or install in UI. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/INSTALL.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/INSTALL.md new file mode 100644 index 0000000..ebb6347 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/INSTALL.md @@ -0,0 +1,7 @@ +# Install + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_document_page" +# or +uv pip install odoo-bringout-oca-ai-ai_oca_bridge_document_page" +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/MODELS.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/MODELS.md new file mode 100644 index 0000000..2889805 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/MODELS.md @@ -0,0 +1,12 @@ +# Models + +Detected core models and extensions in ai_oca_bridge_document_page. + +```mermaid +classDiagram + class document_page +``` + +Notes +- Classes show model technical names; fields omitted for brevity. +- Items listed under _inherit are extensions of existing models. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/OVERVIEW.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/OVERVIEW.md new file mode 100644 index 0000000..0f7e0a5 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/OVERVIEW.md @@ -0,0 +1,6 @@ +# Overview + +Packaged Odoo addon: ai_oca_bridge_document_page. Provides features documented in upstream Odoo 16 under this addon. + +- Source: OCA/OCB 16.0, addon ai_oca_bridge_document_page +- License: LGPL-3 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/REPORTS.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/REPORTS.md new file mode 100644 index 0000000..e0ea35f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/REPORTS.md @@ -0,0 +1,3 @@ +# Reports + +This module does not define custom reports. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/SECURITY.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/SECURITY.md new file mode 100644 index 0000000..e07da9d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/SECURITY.md @@ -0,0 +1,8 @@ +# Security + +This module does not define custom security rules or access controls beyond Odoo defaults. + +Default Odoo security applies: +- Base user access through standard groups +- Model access inherited from dependencies +- No custom row-level security rules diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/TROUBLESHOOTING.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/TROUBLESHOOTING.md new file mode 100644 index 0000000..56853cb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/TROUBLESHOOTING.md @@ -0,0 +1,5 @@ +# Troubleshooting + +- Ensure Python and Odoo environment matches repo guidance. +- Check database connectivity and logs if startup fails. +- Validate that dependent addons listed in DEPENDENCIES.md are installed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/USAGE.md new file mode 100644 index 0000000..f448c3e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/USAGE.md @@ -0,0 +1,7 @@ +# Usage + +Start Odoo including this addon (from repo root): + +```bash +python3 scripts/nix_odoo_web_server.py --db-name mydb --addon ai_oca_bridge_document_page +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/WIZARDS.md b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/WIZARDS.md new file mode 100644 index 0000000..48e790d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/doc/WIZARDS.md @@ -0,0 +1,3 @@ +# Wizards + +This module does not include UI wizards. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_document_page/pyproject.toml b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/pyproject.toml new file mode 100644 index 0000000..83660d2 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_document_page/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "odoo-bringout-oca-ai-ai_oca_bridge_document_page" +version = "16.0.0" +description = "Ai Oca Bridge Document Page - Adds Documents synchronization using AI Bridges" +authors = [ + { name = "Ernad Husremovic", email = "hernad@bring.out.ba" } +] +dependencies = [ + "odoo-bringout-oca-ai-ai_oca_bridge>=16.0.0", + "odoo-bringout-oca-ai-document_page>=16.0.0", + "requests>=2.25.1" +] +readme = "README.md" +requires-python = ">= 3.11" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business", +] + +[project.urls] +homepage = "https://github.com/bringout/0" +repository = "https://github.com/bringout/0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["ai_oca_bridge_document_page"] + +[tool.rye] +managed = true +dev-dependencies = [ + "pytest>=8.4.1", +] diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/README.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/README.md new file mode 100644 index 0000000..e280455 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/README.md @@ -0,0 +1,45 @@ +# Ai Oca Bridge Helpdesk Mgmt + +Odoo addon: ai_oca_bridge_helpdesk_mgmt + +## Installation + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt +``` + +## Dependencies + +This addon depends on: +- ai_oca_bridge +- helpdesk_mgmt + +## Manifest Information + +- **Name**: Ai Oca Bridge Helpdesk Mgmt +- **Version**: 16.0.1.0.0 +- **Category**: N/A +- **License**: AGPL-3 +- **Installable**: False + +## Source + +Based on [OCA/ai](https://github.com/OCA/ai) branch 16.0, addon `ai_oca_bridge_helpdesk_mgmt`. + +## License + +This package maintains the original AGPL-3 license from the upstream Odoo project. + +## Documentation + +- Overview: doc/OVERVIEW.md +- Architecture: doc/ARCHITECTURE.md +- Models: doc/MODELS.md +- Controllers: doc/CONTROLLERS.md +- Wizards: doc/WIZARDS.md +- Install: doc/INSTALL.md +- Usage: doc/USAGE.md +- Configuration: doc/CONFIGURATION.md +- Dependencies: doc/DEPENDENCIES.md +- Troubleshooting: doc/TROUBLESHOOTING.md +- FAQ: doc/FAQ.md diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/README.rst b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/README.rst new file mode 100644 index 0000000..752864f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/README.rst @@ -0,0 +1,83 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=========================== +Ai Oca Bridge Helpdesk Mgmt +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:762ac092261fef4e7e4b88ff7cbd1a88b7dd2100073871981d5ee6bcfb29919f + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fai-lightgray.png?logo=github + :target: https://github.com/OCA/ai/tree/16.0/ai_oca_bridge_helpdesk_mgmt + :alt: OCA/ai +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/ai-16-0/ai-16-0-ai_oca_bridge_helpdesk_mgmt + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/ai&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is a glue module between AI OCA Bridge and helpdesk +management. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Dixmit + +Contributors +------------ + +- `Dixmit `__ + + - Enric Tobella + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/ai `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__manifest__.py b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__manifest__.py new file mode 100644 index 0000000..733a722 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/__manifest__.py @@ -0,0 +1,14 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Ai Oca Bridge Helpdesk Mgmt", + "summary": """Integrate AI Bridge with Helpdesk Management""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Dixmit,Odoo Community Association (OCA)", + "website": "https://github.com/OCA/ai", + "depends": ["ai_oca_bridge", "helpdesk_mgmt"], + "data": [], + "demo": [], +} diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/ai_oca_bridge_helpdesk_mgmt.pot b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/ai_oca_bridge_helpdesk_mgmt.pot new file mode 100644 index 0000000..8d0ec2a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/ai_oca_bridge_helpdesk_mgmt.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/bs.po b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/bs.po new file mode 100644 index 0000000..f040e18 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/bs.po @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "Tiket helpdesk-a" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es.po b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es.po new file mode 100644 index 0000000..2af4bb9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-15 22:40+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "Ticket de Helpdesk" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es_VE.po b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es_VE.po new file mode 100644 index 0000000..858b97b --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/es_VE.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 01:25+0000\n" +"Last-Translator: \"Leonardo J. Caballero G.\" \n" +"Language-Team: none\n" +"Language: es_VE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "Ticket de Helpdesk" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/it.po b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/it.po new file mode 100644 index 0000000..6fb1fcd --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/it.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-08-16 14:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "Ticket assistenza clienti" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/pt_BR.po b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/pt_BR.po new file mode 100644 index 0000000..acdbb26 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/i18n/pt_BR.po @@ -0,0 +1,20 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * ai_oca_bridge_helpdesk_mgmt +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: ai_oca_bridge_helpdesk_mgmt +#: model:ir.model,name:ai_oca_bridge_helpdesk_mgmt.model_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "" diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/__init__.py b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/__init__.py new file mode 100644 index 0000000..8e082e7 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/__init__.py @@ -0,0 +1 @@ +from . import helpdesk_ticket diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/helpdesk_ticket.py b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/helpdesk_ticket.py new file mode 100644 index 0000000..c575202 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/models/helpdesk_ticket.py @@ -0,0 +1,10 @@ +# Copyright 2025 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class HelpdeskTicket(models.Model): + + _name = "helpdesk.ticket" + _inherit = ["helpdesk.ticket", "ai.bridge.thread"] diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/CONTRIBUTORS.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..45c321e --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Dixmit](www.dixmit.com) + - Enric Tobella \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/DESCRIPTION.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/DESCRIPTION.md new file mode 100644 index 0000000..c770cf9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/readme/DESCRIPTION.md @@ -0,0 +1 @@ +This module is a glue module between AI OCA Bridge and helpdesk management. \ No newline at end of file diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/icon.png b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..67b4853a5eddb9eeae5db4fa5e8839fd355581fe GIT binary patch literal 93215 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 + + + + + + + + + + + + + + + + + + + + + + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/index.html b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/index.html new file mode 100644 index 0000000..c4662e9 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/ai_oca_bridge_helpdesk_mgmt/static/description/index.html @@ -0,0 +1,433 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Ai Oca Bridge Helpdesk Mgmt

+ +

Beta License: AGPL-3 OCA/ai Translate me on Weblate Try me on Runboat

+

This module is a glue module between AI OCA Bridge and helpdesk +management.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Dixmit
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/ai project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/ARCHITECTURE.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/ARCHITECTURE.md new file mode 100644 index 0000000..9d34b1a --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/ARCHITECTURE.md @@ -0,0 +1,32 @@ +# Architecture + +```mermaid +flowchart TD + U[Users] -->|HTTP| V[Views and QWeb Templates] + V --> C[Controllers] + V --> W[Wizards – Transient Models] + C --> M[Models and ORM] + W --> M + M --> R[Reports] + DX[Data XML] --> M + S[Security – ACLs and Groups] -. enforces .-> M + + subgraph Ai_oca_bridge_helpdesk_mgmt Module - ai_oca_bridge_helpdesk_mgmt + direction LR + M:::layer + W:::layer + C:::layer + V:::layer + R:::layer + S:::layer + DX:::layer + end + + classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px +``` + +Notes +- Views include tree/form/kanban templates and report templates. +- Controllers provide website/portal routes when present. +- Wizards are UI flows implemented with `models.TransientModel`. +- Data XML loads data/demo records; Security defines groups and access. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONFIGURATION.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONFIGURATION.md new file mode 100644 index 0000000..9079678 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONFIGURATION.md @@ -0,0 +1,3 @@ +# Configuration + +Refer to Odoo settings for ai_oca_bridge_helpdesk_mgmt. Configure related models, access rights, and options as needed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONTROLLERS.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONTROLLERS.md new file mode 100644 index 0000000..f628e77 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/CONTROLLERS.md @@ -0,0 +1,3 @@ +# Controllers + +This module does not define custom HTTP controllers. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/DEPENDENCIES.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/DEPENDENCIES.md new file mode 100644 index 0000000..88e7723 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/DEPENDENCIES.md @@ -0,0 +1,6 @@ +# Dependencies + +This addon depends on: + +- [ai_oca_bridge](../../odoo-bringout-oca-ai-ai_oca_bridge) +- helpdesk_mgmt diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/FAQ.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/FAQ.md new file mode 100644 index 0000000..2c98466 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/FAQ.md @@ -0,0 +1,4 @@ +# FAQ + +- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged). +- Q: How to enable? A: Start server with --addon ai_oca_bridge_helpdesk_mgmt or install in UI. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/INSTALL.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/INSTALL.md new file mode 100644 index 0000000..77e572b --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/INSTALL.md @@ -0,0 +1,7 @@ +# Install + +```bash +pip install odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt" +# or +uv pip install odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt" +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/MODELS.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/MODELS.md new file mode 100644 index 0000000..ea18080 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/MODELS.md @@ -0,0 +1,12 @@ +# Models + +Detected core models and extensions in ai_oca_bridge_helpdesk_mgmt. + +```mermaid +classDiagram + class helpdesk_ticket +``` + +Notes +- Classes show model technical names; fields omitted for brevity. +- Items listed under _inherit are extensions of existing models. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/OVERVIEW.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/OVERVIEW.md new file mode 100644 index 0000000..23cbebc --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/OVERVIEW.md @@ -0,0 +1,6 @@ +# Overview + +Packaged Odoo addon: ai_oca_bridge_helpdesk_mgmt. Provides features documented in upstream Odoo 16 under this addon. + +- Source: OCA/OCB 16.0, addon ai_oca_bridge_helpdesk_mgmt +- License: LGPL-3 diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/REPORTS.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/REPORTS.md new file mode 100644 index 0000000..e0ea35f --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/REPORTS.md @@ -0,0 +1,3 @@ +# Reports + +This module does not define custom reports. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/SECURITY.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/SECURITY.md new file mode 100644 index 0000000..e07da9d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/SECURITY.md @@ -0,0 +1,8 @@ +# Security + +This module does not define custom security rules or access controls beyond Odoo defaults. + +Default Odoo security applies: +- Base user access through standard groups +- Model access inherited from dependencies +- No custom row-level security rules diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/TROUBLESHOOTING.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/TROUBLESHOOTING.md new file mode 100644 index 0000000..56853cb --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/TROUBLESHOOTING.md @@ -0,0 +1,5 @@ +# Troubleshooting + +- Ensure Python and Odoo environment matches repo guidance. +- Check database connectivity and logs if startup fails. +- Validate that dependent addons listed in DEPENDENCIES.md are installed. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/USAGE.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/USAGE.md new file mode 100644 index 0000000..e8bad7d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/USAGE.md @@ -0,0 +1,7 @@ +# Usage + +Start Odoo including this addon (from repo root): + +```bash +python3 scripts/nix_odoo_web_server.py --db-name mydb --addon ai_oca_bridge_helpdesk_mgmt +``` diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/WIZARDS.md b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/WIZARDS.md new file mode 100644 index 0000000..48e790d --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/doc/WIZARDS.md @@ -0,0 +1,3 @@ +# Wizards + +This module does not include UI wizards. diff --git a/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/pyproject.toml b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/pyproject.toml new file mode 100644 index 0000000..c353776 --- /dev/null +++ b/odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "odoo-bringout-oca-ai-ai_oca_bridge_helpdesk_mgmt" +version = "16.0.0" +description = "Ai Oca Bridge Helpdesk Mgmt - Integrate AI Bridge with Helpdesk Management" +authors = [ + { name = "Ernad Husremovic", email = "hernad@bring.out.ba" } +] +dependencies = [ + "odoo-bringout-oca-ai-ai_oca_bridge>=16.0.0", + "odoo-bringout-oca-ai-helpdesk_mgmt>=16.0.0", + "requests>=2.25.1" +] +readme = "README.md" +requires-python = ">= 3.11" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Office/Business", +] + +[project.urls] +homepage = "https://github.com/bringout/0" +repository = "https://github.com/bringout/0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = ["ai_oca_bridge_helpdesk_mgmt"] + +[tool.rye] +managed = true +dev-dependencies = [ + "pytest>=8.4.1", +]
+ + + +Odoo Community Association + +
+

AI OCA Bridge

+ +

Beta License: AGPL-3 OCA/ai Translate me on Weblate Try me on Runboat

+

This module is used to create a bridge between Odoo and other AI systems +like n8n.

+

Table of contents

+ +
+

Use Cases / Context

+

Right now, there are 2 different approaches for AI integration with +Odoo:

+
    +
  1. Make everything inside Odoo.
  2. +
  3. Make it using other tools and integrate Odoo with these tools.
  4. +
+

IMO, it would be better to make use of option 2 for different reasons:

+
    +
  • Odoo server is intended as a transactional system. AI systems requires +other kind of characteristics
  • +
  • Everything changes too fast. I am not confident that Odoo can keep the +pace in this topic
  • +
  • There are OSS tools that fills the gap perfectly and are created just +for this topic.
  • +
+

Anyway, OCA is open to everyone and we don’t intend to force an +opinionated way of doing. For this reason, we have this module, that can +be used as Bridge with AI systems.

+
+
+

Configuration

+

As an administrator access AI Bridge\AI Bridge.

+

Create a new bridge. Define the name, model, url and configuration.

+

In order to improve the view of the AI configuration, use groups and +domain to set better filters.

+
+

Payload Configuration

+

On the external system, you will receive a POST payload. The data +included will be the following:

+
+

General

+
    +
  • _odoo: Standard data to identify the Odoo Database
  • +
  • _model: Model of the related object
  • +
  • _id: Id of the related object
  • +
  • _response_url: Url to call with the response in case of async calls
  • +
+
+
+

Record Payload

+

Adds a new item called record with all the fields.

+
+
+

Record Payload (v0)

+

Adds all the fields directly on the payload. It will be removed on 17.0.

+
+
+
+

Asynchronous and synchronous calls

+

The new system allows asynchronous and synchronous calls. Asynchronous +calls makes sense when the task to be processed don’t need to be +immediate. For example, reviewing an invoice and leave a comment with +the result. The same would happen with a chat message. We expect that +the system will leave time to the AI to answer and Odoo’s user can do +other things.

+

Meanwhile, Synchronous calls will froze odoo system and wait for an +answer. This makes sense when we expect some feedback from odoo user. It +makes sense, when we open an action for example.

+

In the synchronous call, the result is processed when the AI system +answers on the webhook. On the other hand, it will be processed +automatically on the synchronous call.

+
+
+

Result processing

+

With the answers of the system we expect to do something about it. We +have the following options:

+
+

No processing

+

In this case, the result will do nothing

+
+
+

Post a Message

+

We will post a message on the original thread of the system. The thread +is computed by a function, so it can be overriden in future modules. It +expects the keyword arguments of the message_post function.

+
+
+

Action

+

It expects to launch an action on the user interface. It only makes +sense on synchronous calls.

+

It expects an action item with the following parameters:

+
    +
  • action: xmlid of the action
  • +
  • context: Context to pass to the action (not required)
  • +
  • res_id: Id of the resource (not required)
  • +
+
+
+
+
+

Usage

+

Use the bolt widget in the chatter to execute the different AI options.

+

The options will be filtered according to the configuration.

+
+
+

Known issues / Roadmap

+
    +
  • Define examples to use and import
  • +
  • Allow child fields. Right now, only first level fields are accepted.
  • +
  • Information popover is not working properly when there is large data.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Dixmit
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/ai project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+