@ -0,0 +1,75 @@ | 
				
			|||
# Generative AI for Odoo 18 | 
				
			|||
 | 
				
			|||
[](https://www.odoo.com) | 
				
			|||
[](https://opensource.org/licenses/MIT) | 
				
			|||
 | 
				
			|||
## Overview | 
				
			|||
 | 
				
			|||
The Generative AI for Odoo 18 module allows users to generate website snippets using AI-powered text prompts directly within the Odoo Website Editor. It seamlessly integrates with OpenAI and OpenRouter to enhance content creation and streamline web development workflows. | 
				
			|||
 | 
				
			|||
## Features | 
				
			|||
 | 
				
			|||
- 💾 Instantly generate custom website snippets from text prompts using OpenAI or OpenRouter. | 
				
			|||
- 🧠 Create, preview, and insert AI-generated snippets directly within Odoo’s website editor. | 
				
			|||
- ⚙️ Easily configure AI provider, model, and token limits from Odoo settings for full control. | 
				
			|||
 | 
				
			|||
## Screenshots | 
				
			|||
 | 
				
			|||
Here are some glimpses of Generative AI | 
				
			|||
 | 
				
			|||
### User Interface of Website module | 
				
			|||
 | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 1" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 2" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 3" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 4" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 5" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
<div> | 
				
			|||
  <tr> | 
				
			|||
    <td align="center"> | 
				
			|||
      <img src="static/description/assets/screenshots/Screenshot1.png" alt="Screenshot 6" width="500" style="border: none;"/> | 
				
			|||
    </td> | 
				
			|||
  </tr> | 
				
			|||
</div> | 
				
			|||
 | 
				
			|||
 | 
				
			|||
 | 
				
			|||
## Prerequisites | 
				
			|||
 | 
				
			|||
Before you begin, ensure you have the following installed: | 
				
			|||
 | 
				
			|||
- An active Odoo Community/Enterprise Edition instance (local or hosted) | 
				
			|||
 | 
				
			|||
## Configuration | 
				
			|||
- Ensure the OpenAI Python package is installed. | 
				
			|||
 | 
				
			|||
@ -0,0 +1,23 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from . import controllers | 
				
			|||
from . import models | 
				
			|||
@ -0,0 +1,64 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
{ | 
				
			|||
    'name': 'Generative AI In Snippet Creation', | 
				
			|||
    'version': '18.0.1.0.0', | 
				
			|||
    'category': 'Generative AI/Generative AI', | 
				
			|||
    'summary': 'To integrate AI tools in website snippet creation.', | 
				
			|||
    'description': """This module contains details about Generative AI in website.""", | 
				
			|||
    'author': 'Cybrosys Techno Solutions', | 
				
			|||
    'company': 'Cybrosys Techno Solutions', | 
				
			|||
    'maintainer': 'Cybrosys Techno Solutions', | 
				
			|||
    'website': 'https://www.cybrosys.com', | 
				
			|||
    'depends': [ | 
				
			|||
        'base', 'website', 'web', 'web_editor' | 
				
			|||
    ], | 
				
			|||
    'external_dependencies': { | 
				
			|||
        'python': [ | 
				
			|||
            'openai', | 
				
			|||
            ], | 
				
			|||
    }, | 
				
			|||
    'data': [ | 
				
			|||
        'security/ir.model.access.csv', | 
				
			|||
        'data/test_data.xml', | 
				
			|||
        'views/res_config_settings_views.xml', | 
				
			|||
        'views/snippets/s_snippet_group_with_ai_content.xml', | 
				
			|||
        'views/snippets.xml', | 
				
			|||
    ], | 
				
			|||
    'assets': { | 
				
			|||
        'web_editor.assets_wysiwyg': [ | 
				
			|||
            'generative_ai/static/src/xml/website_dialogue_box.xml', | 
				
			|||
            'generative_ai/static/src/js/ai_button_action.js', | 
				
			|||
            ('include', 'web._assets_helpers'), | 
				
			|||
            'generative_ai/static/src/js/title.js', | 
				
			|||
        ], | 
				
			|||
        'web.assets_backend': [ | 
				
			|||
            'generative_ai/static/src/img/placeholder.png', | 
				
			|||
            'generative_ai/static/src/css/snippets.css', | 
				
			|||
        ], | 
				
			|||
    }, | 
				
			|||
    'images': ['static/description/banner.jpg'], | 
				
			|||
    'license': 'AGPL-3', | 
				
			|||
    'installable': True, | 
				
			|||
    'auto_install': False, | 
				
			|||
    'application': True, | 
				
			|||
} | 
				
			|||
@ -0,0 +1,22 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from . import main | 
				
			|||
@ -0,0 +1,400 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
import hashlib | 
				
			|||
import re | 
				
			|||
import requests | 
				
			|||
from markupsafe import Markup | 
				
			|||
from openai import OpenAI | 
				
			|||
from odoo import http | 
				
			|||
from odoo.http import request | 
				
			|||
 | 
				
			|||
 | 
				
			|||
def preprocess_ai_output(text): | 
				
			|||
    """ | 
				
			|||
    Preprocesses AI-generated text by carefully extracting and closing CSS rules. | 
				
			|||
    Args: | 
				
			|||
        text (str): Input text containing HTML and CSS | 
				
			|||
    Returns: | 
				
			|||
        str: Preprocessed text with properly closed CSS | 
				
			|||
    """ | 
				
			|||
    text = re.sub(r'^```(html|css|plaintext)?\s*', '', text.strip(), flags=re.MULTILINE) | 
				
			|||
    text = re.sub(r'\s*```$', '', text, flags=re.MULTILINE) | 
				
			|||
    # Check if style tags already exist | 
				
			|||
    style_match = re.search(r'<style>(.*?)</style>', text, re.DOTALL) | 
				
			|||
    if style_match: | 
				
			|||
        return text | 
				
			|||
    css_lines = [] | 
				
			|||
    html_lines = [] | 
				
			|||
    current_rule = [] | 
				
			|||
    in_css_block = False | 
				
			|||
    for line in text.split('\n'): | 
				
			|||
        stripped_line = line.strip() | 
				
			|||
        if re.match(r'^(\.|#|[a-zA-Z])[\w\s,#>:.()-]+\s*{', stripped_line): | 
				
			|||
            in_css_block = True | 
				
			|||
            current_rule = [line] | 
				
			|||
        elif in_css_block: | 
				
			|||
            current_rule.append(line) | 
				
			|||
            if stripped_line.endswith('}'): | 
				
			|||
                css_lines.extend(current_rule) | 
				
			|||
                current_rule = [] | 
				
			|||
                in_css_block = False | 
				
			|||
        else: | 
				
			|||
            if stripped_line: | 
				
			|||
                html_lines.append(line) | 
				
			|||
    if current_rule: | 
				
			|||
        if not current_rule[-1].strip().endswith('}'): | 
				
			|||
            current_rule[-1] = current_rule[-1] + '\n}' | 
				
			|||
        css_lines.extend(current_rule) | 
				
			|||
    if css_lines: | 
				
			|||
        css_output = "<style>\n" + "\n".join(css_lines) + "\n</style>" | 
				
			|||
    else: | 
				
			|||
        css_output = "" | 
				
			|||
    html_output = "\n".join(html_lines) | 
				
			|||
    final_output = f"{css_output}\n{html_output}" if css_output else html_output | 
				
			|||
    return final_output.strip() | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class SnippetGenerator(http.Controller): | 
				
			|||
 | 
				
			|||
 | 
				
			|||
    def _generate_unique_id(self, snippet_name): | 
				
			|||
        """Generate a unique ID for the snippet to avoid CSS conflicts""" | 
				
			|||
        return hashlib.md5(f"{snippet_name}_{request.session.sid}".encode()).hexdigest()[ | 
				
			|||
               :8] | 
				
			|||
 | 
				
			|||
    def _generate_openai_response(self, prompt, api_key, model_id, max_tokens, | 
				
			|||
                                  snippet_name): | 
				
			|||
        """Helper method for OpenAI API calls""" | 
				
			|||
        response = OpenAI(api_key=api_key).chat.completions.create( | 
				
			|||
            model=model_id, | 
				
			|||
            messages=[ | 
				
			|||
                {"role": "system", | 
				
			|||
                 "content": "You are a helpful assistant that generates website snippets similar to Odoo's inbuilt website snippets. " | 
				
			|||
                            "Create responsive snippets using Bootstrap classes, ensuring proper container and row/column structure. " | 
				
			|||
                            "Use placeholder images from 'https://via.placeholder.com/' with appropriate sizes. " | 
				
			|||
                            "Return only clean HTML and CSS without any wrapper divs or preview elements. " | 
				
			|||
                            "Do not include any code block markers (```). " | 
				
			|||
                            "Focus on creating functional, beautiful snippets that work well in Odoo's website builder. " | 
				
			|||
                            "Use modern design principles with good typography, spacing, and colors." | 
				
			|||
                 }, | 
				
			|||
                {"role": "user", "content": prompt} | 
				
			|||
            ], | 
				
			|||
            max_tokens=int(max_tokens), | 
				
			|||
            temperature=0.7 | 
				
			|||
        ) | 
				
			|||
        return response.choices[0].message.content.strip() | 
				
			|||
 | 
				
			|||
    def _generate_openrouter_response(self, prompt, api_key, model_id, max_tokens, | 
				
			|||
                                      snippet_name): | 
				
			|||
        """Helper method for OpenRouter API calls with improved error handling""" | 
				
			|||
        url = "https://openrouter.ai/api/v1/chat/completions" | 
				
			|||
        headers = { | 
				
			|||
            "Authorization": f"Bearer {api_key}", | 
				
			|||
            "HTTP-Referer": request.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                'web.base.url', ''), | 
				
			|||
            "X-Title": "Odoo Integration" | 
				
			|||
        } | 
				
			|||
        payload = { | 
				
			|||
            "model": model_id, | 
				
			|||
            "messages": [ | 
				
			|||
                {"role": "system", | 
				
			|||
                 "content": "You are a helpful assistant that generates website snippets similar to Odoo's inbuilt website snippets. " | 
				
			|||
                            "Create responsive snippets using Bootstrap classes, ensuring proper container and row/column structure. " | 
				
			|||
                            "Use placeholder images from 'https://via.placeholder.com/' with appropriate sizes. " | 
				
			|||
                            "Return only clean HTML and CSS without any wrapper divs or preview elements. " | 
				
			|||
                            "Do not include any code block markers (```). " | 
				
			|||
                            "Focus on creating functional, beautiful snippets that work well in Odoo's website builder. " | 
				
			|||
                            "Use modern design principles with good typography, spacing, and colors." | 
				
			|||
                 }, | 
				
			|||
                {"role": "user", "content": prompt} | 
				
			|||
            ], | 
				
			|||
            "max_tokens": int(max_tokens), | 
				
			|||
            "temperature": 0.7 | 
				
			|||
        } | 
				
			|||
        response = requests.post(url, headers=headers, json=payload) | 
				
			|||
        if response.status_code != 200: | 
				
			|||
            raise Exception( | 
				
			|||
                f"OpenRouter API error: {response.status_code} - {response.text}") | 
				
			|||
        json_response = response.json() | 
				
			|||
        if 'choices' not in json_response or not json_response['choices']: | 
				
			|||
            raise Exception("Invalid response format from OpenRouter API") | 
				
			|||
        choice = json_response['choices'][0] | 
				
			|||
        if 'message' not in choice: | 
				
			|||
            raise Exception("No message in OpenRouter API response") | 
				
			|||
        message = choice['message'] | 
				
			|||
        content = message.get('content', '') | 
				
			|||
        reasoning = message.get('reasoning', '') | 
				
			|||
        partial_content = content.strip() if content else reasoning.strip() | 
				
			|||
        if choice.get('finish_reason') == 'length' and partial_content: | 
				
			|||
            completion_prompt = f""" | 
				
			|||
            You previously started generating a website snippet but it was cut off. | 
				
			|||
            Please complete the snippet based on this partial content: | 
				
			|||
            {partial_content} | 
				
			|||
            Continue from where it was cut off and make sure the HTML/CSS is complete and valid. | 
				
			|||
            """ | 
				
			|||
            completion_payload = { | 
				
			|||
                "model": model_id, | 
				
			|||
                "messages": [ | 
				
			|||
                    {"role": "system", | 
				
			|||
                     "content": "You are completing a partially generated website snippet. " | 
				
			|||
                                "Return only HTML/CSS code to complete the snippet, don't start over."}, | 
				
			|||
                    {"role": "user", "content": completion_prompt} | 
				
			|||
                ], | 
				
			|||
                "max_tokens": int(max_tokens), | 
				
			|||
                "temperature": 0.7 | 
				
			|||
            } | 
				
			|||
            try: | 
				
			|||
                completion_response = requests.post(url, headers=headers, | 
				
			|||
                                                    json=completion_payload) | 
				
			|||
                completion_response.raise_for_status() | 
				
			|||
                completion_json = completion_response.json() | 
				
			|||
                if 'choices' in completion_json and completion_json['choices']: | 
				
			|||
                    completion_message = completion_json['choices'][0]['message'] | 
				
			|||
                    completion_content = completion_message.get('content', '') | 
				
			|||
                    if completion_content: | 
				
			|||
                        cleaned_completion = re.sub(r"^```(html|css)?\s*", "", | 
				
			|||
                                                    completion_content.strip(), | 
				
			|||
                                                    flags=re.MULTILINE) | 
				
			|||
                        cleaned_completion = re.sub(r"\s*```$", "", cleaned_completion, | 
				
			|||
                                                    flags=re.MULTILINE) | 
				
			|||
                        combined_content = partial_content + "\n" + cleaned_completion | 
				
			|||
                        return combined_content | 
				
			|||
                return partial_content | 
				
			|||
            except Exception: | 
				
			|||
                return partial_content + "\n<!-- Note: This snippet is incomplete. The system attempted to complete it but encountered an error. -->" | 
				
			|||
        if content: | 
				
			|||
            return content.strip() | 
				
			|||
        elif reasoning: | 
				
			|||
            return reasoning.strip() | 
				
			|||
        else: | 
				
			|||
            raise Exception( | 
				
			|||
                "Empty response from AI model. Please try again with a " | 
				
			|||
                "different prompt or model.") | 
				
			|||
 | 
				
			|||
    @http.route('/website/generate_snippet', type='json', auth='user', website=True) | 
				
			|||
    def generate_snippet(self, **kwargs): | 
				
			|||
        """Single route to handle both OpenAI and OpenRouter snippet generation""" | 
				
			|||
        prompt = kwargs.get('prompt') | 
				
			|||
        snippet_name = kwargs.get('name') | 
				
			|||
        if not prompt: | 
				
			|||
            return {'error': "Prompt is required"} | 
				
			|||
        try: | 
				
			|||
            api_key = request.env['ir.config_parameter'].sudo().get_param('api_key') | 
				
			|||
            ai_system = request.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                'generative_ai_systems') | 
				
			|||
            max_tokens = request.env['ir.config_parameter'].sudo().get_param('max_token') | 
				
			|||
            if not api_key: | 
				
			|||
                return {'error': "API key not configured"} | 
				
			|||
            unique_id = self._generate_unique_id(snippet_name) | 
				
			|||
            try: | 
				
			|||
                if ai_system == 'openai': | 
				
			|||
                    model_id = request.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                        'model_id') | 
				
			|||
                    if not model_id: | 
				
			|||
                        return {'error': "No OpenAI model selected"} | 
				
			|||
                    snippet_text = self._generate_openai_response(prompt, api_key, | 
				
			|||
                                                                  model_id, max_tokens, | 
				
			|||
                                                                  snippet_name) | 
				
			|||
                elif ai_system == 'openrouter': | 
				
			|||
                    model_id = request.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                        'openrouter_model_id') | 
				
			|||
                    if not model_id or model_id == 'none': | 
				
			|||
                        return {'error': "No OpenRouter model selected"} | 
				
			|||
                    snippet_text = self._generate_openrouter_response(prompt, api_key, | 
				
			|||
                                                                      model_id, | 
				
			|||
                                                                      max_tokens, | 
				
			|||
                                                                      snippet_name) | 
				
			|||
                else: | 
				
			|||
                    return {'error': "Invalid AI system selected"} | 
				
			|||
            except Exception as api_error: | 
				
			|||
                return {'error': f"API Error: {str(api_error)}", 'retry': True} | 
				
			|||
            # Check if we got a valid response | 
				
			|||
            if not snippet_text or snippet_text.strip() == "": | 
				
			|||
                return { | 
				
			|||
                    'error': "AI did not generate any content. Please try again with a different prompt or model.", | 
				
			|||
                    'retry': True | 
				
			|||
                } | 
				
			|||
            processed_snippet_text = preprocess_ai_output(snippet_text) | 
				
			|||
            preview_style = f""" | 
				
			|||
<style> | 
				
			|||
.snippet-preview-{unique_id} {{ | 
				
			|||
    position: relative !important; | 
				
			|||
    min-width: 600px !important; | 
				
			|||
    min-height: 400px !important; | 
				
			|||
    width: 100% !important; | 
				
			|||
    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; | 
				
			|||
    border-radius: 12px !important; | 
				
			|||
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important; | 
				
			|||
    overflow: visible !important; | 
				
			|||
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; | 
				
			|||
    display: flex !important; | 
				
			|||
    flex-direction: column !important; | 
				
			|||
    align-items: center !important; | 
				
			|||
    justify-content: center !important; | 
				
			|||
    padding: 20px !important; | 
				
			|||
    box-sizing: border-box !important; | 
				
			|||
    cursor: pointer !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-preview-{unique_id}:hover {{ | 
				
			|||
    transform: translateY(-5px) !important; | 
				
			|||
    box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15) !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-preview-{unique_id}::before {{ | 
				
			|||
    content: '' !important; | 
				
			|||
    position: absolute !important; | 
				
			|||
    top: 0 !important; | 
				
			|||
    left: 0 !important; | 
				
			|||
    right: 0 !important; | 
				
			|||
    height: 4px !important; | 
				
			|||
    background: linear-gradient(90deg, #667eea 0%, #764ba2 100%) !important; | 
				
			|||
    z-index: 1 !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-name-{unique_id} {{ | 
				
			|||
    position: absolute !important; | 
				
			|||
    top: -10px !important; | 
				
			|||
    left: 50% !important; | 
				
			|||
    transform: translateX(-50%) !important; | 
				
			|||
    background: rgba(255, 255, 255, 0.95) !important; | 
				
			|||
    backdrop-filter: blur(10px) !important; | 
				
			|||
    -webkit-backdrop-filter: blur(10px) !important; | 
				
			|||
    color: #2d3748 !important; | 
				
			|||
    padding: 12px 20px !important; | 
				
			|||
    border-radius: 25px !important; | 
				
			|||
    font-size: 16px !important; | 
				
			|||
    font-weight: 600 !important; | 
				
			|||
    letter-spacing: 0.5px !important; | 
				
			|||
    opacity: 0 !important; | 
				
			|||
    visibility: hidden !important; | 
				
			|||
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; | 
				
			|||
    z-index: 1000 !important; | 
				
			|||
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important; | 
				
			|||
    border: 1px solid rgba(255, 255, 255, 0.3) !important; | 
				
			|||
    white-space: nowrap !important; | 
				
			|||
    pointer-events: none !important; | 
				
			|||
    min-width: 120px !important; | 
				
			|||
    text-align: center !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-preview-{unique_id}:hover .snippet-name-{unique_id} {{ | 
				
			|||
    opacity: 1 !important; | 
				
			|||
    visibility: visible !important; | 
				
			|||
    transform: translateX(-50%) translateY(-8px) !important; | 
				
			|||
    pointer-events: auto !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
/* Alternative hover trigger - also trigger on any child hover */ | 
				
			|||
.snippet-preview-{unique_id} *:hover ~ .snippet-name-{unique_id}, | 
				
			|||
.snippet-preview-{unique_id} .snippet-content-{unique_id}:hover ~ .snippet-name-{unique_id} {{ | 
				
			|||
    opacity: 1 !important; | 
				
			|||
    visibility: visible !important; | 
				
			|||
    transform: translateX(-50%) translateY(-8px) !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-content-{unique_id} {{ | 
				
			|||
    width: 100% !important; | 
				
			|||
    max-width: 100% !important; | 
				
			|||
    z-index: 2 !important; | 
				
			|||
    position: relative !important; | 
				
			|||
    background: white !important; | 
				
			|||
    border-radius: 8px !important; | 
				
			|||
    padding: 20px !important; | 
				
			|||
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08) !important; | 
				
			|||
    margin-top: 30px !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
/* Ensure content doesn't overflow */ | 
				
			|||
.snippet-content-{unique_id} * {{ | 
				
			|||
    max-width: 100% !important; | 
				
			|||
    box-sizing: border-box !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
/* Style for cards and common elements */ | 
				
			|||
.snippet-content-{unique_id} .card {{ | 
				
			|||
    border: none !important; | 
				
			|||
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1) !important; | 
				
			|||
    border-radius: 8px !important; | 
				
			|||
    transition: transform 0.2s ease !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-content-{unique_id} .card:hover {{ | 
				
			|||
    transform: translateY(-2px) !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-content-{unique_id} .btn {{ | 
				
			|||
    border-radius: 6px !important; | 
				
			|||
    font-weight: 500 !important; | 
				
			|||
    transition: all 0.2s ease !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-content-{unique_id} .btn-primary {{ | 
				
			|||
    background: linear-gradient(45deg, #667eea, #764ba2) !important; | 
				
			|||
    border: none !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
.snippet-content-{unique_id} .btn-primary:hover {{ | 
				
			|||
    background: linear-gradient(45deg, #5a67d8, #6b46c1) !important; | 
				
			|||
    transform: translateY(-1px) !important; | 
				
			|||
}} | 
				
			|||
 | 
				
			|||
/* Fallback JavaScript-free hover detection */ | 
				
			|||
.snippet-preview-{unique_id}:focus-within .snippet-name-{unique_id} {{ | 
				
			|||
    opacity: 1 !important; | 
				
			|||
    visibility: visible !important; | 
				
			|||
    transform: translateX(-50%) translateY(-8px) !important; | 
				
			|||
}} | 
				
			|||
</style>""" | 
				
			|||
 | 
				
			|||
            # Wrap content with unique scoped classes | 
				
			|||
            wrapped_content = f""" | 
				
			|||
{preview_style} | 
				
			|||
<div class="snippet-preview-{unique_id}" title="{snippet_name}"> | 
				
			|||
    <div class="snippet-name-{unique_id}">{snippet_name}</div> | 
				
			|||
    <div class="snippet-content-{unique_id}"> | 
				
			|||
        {processed_snippet_text} | 
				
			|||
    </div> | 
				
			|||
</div> | 
				
			|||
""" | 
				
			|||
            if not wrapped_content or wrapped_content.strip() == "": | 
				
			|||
                return { | 
				
			|||
                    'error': "Could not process AI output into a valid snippet. Please try again.", | 
				
			|||
                    'retry': True | 
				
			|||
                } | 
				
			|||
            snippet = request.env['website.snippet.data'].sudo().create({ | 
				
			|||
                'name': snippet_name, | 
				
			|||
                'content': Markup(wrapped_content), | 
				
			|||
                'image_url': '/generative_ai/static/src/img/placeholder.png', | 
				
			|||
                'is_ai_generated': True, | 
				
			|||
            }) | 
				
			|||
            request.env['ir.qweb'].clear_caches() | 
				
			|||
            request.env['ir.ui.view'].clear_caches() | 
				
			|||
            return { | 
				
			|||
                'success': True, | 
				
			|||
                'snippet': snippet, | 
				
			|||
                'snippet_id': snippet.id, | 
				
			|||
                'content': snippet.content, | 
				
			|||
                'image_url': '/generative_ai/static/src/img/placeholder.png' | 
				
			|||
            } | 
				
			|||
        except Exception as e: | 
				
			|||
            return {'error': str(e), 'retry': True} | 
				
			|||
@ -0,0 +1,17 @@ | 
				
			|||
<?xml version="1.0" encoding="UTF-8"?> | 
				
			|||
<odoo noupdate="1"> | 
				
			|||
    <data> | 
				
			|||
        <record id="default_ai_snippet" model="website.snippet.data"> | 
				
			|||
            <field name="name">Default AI Snippet</field> | 
				
			|||
            <field name="content"><![CDATA[ | 
				
			|||
                <div class="default-ai-snippet" style="background-color: #f4f4f4; padding: 20px; text-align: center; border-radius: 10px;"> | 
				
			|||
                    <h1 style="color: #007bff;">Welcome to AI Generated Snippets</h1> | 
				
			|||
                    <img src="generative_ai/static/src/img/placeholder.png" alt="AI Snippet Image" style="width: 350px; height: auto; margin: 10px auto; display: block;"> | 
				
			|||
                    <p style="color: #333; font-size: 45px;">This is a default snippet that showcases AI capabilities.</p> | 
				
			|||
                </div> | 
				
			|||
            ]]></field> | 
				
			|||
            <field name="image_url">generative_ai/static/src/img/placeholder.png</field> | 
				
			|||
            <field name="is_ai_generated">True</field> | 
				
			|||
        </record> | 
				
			|||
    </data> | 
				
			|||
</odoo> | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
## Module <generative_ai> | 
				
			|||
 | 
				
			|||
#### 21.07.2025 | 
				
			|||
#### Version 18.0.1.0.0 | 
				
			|||
##### ADD | 
				
			|||
- Initial commit for Generative AI. | 
				
			|||
@ -0,0 +1,23 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from . import res_config_settings | 
				
			|||
from . import website_snippet_data | 
				
			|||
@ -0,0 +1,141 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
import requests | 
				
			|||
from odoo import api, fields, models | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class ResConfigSettings(models.TransientModel): | 
				
			|||
    _inherit = 'res.config.settings' | 
				
			|||
 | 
				
			|||
    generative_ai_systems = fields.Selection([ | 
				
			|||
        ('openai', 'Open AI'), | 
				
			|||
        ('openrouter', 'OpenRouter') | 
				
			|||
    ], string='Generative AI Systems') | 
				
			|||
    api_key = fields.Char('API Key', copy=False) | 
				
			|||
    max_token = fields.Char(string='Maximum tokens', | 
				
			|||
                            default=4096, config_parameter='generative_ai.max_token') | 
				
			|||
    model_id = fields.Selection(selection=[ | 
				
			|||
        ('gpt-3.5-turbo', 'gpt-3.5-turbo'), | 
				
			|||
        ('gpt-4-turbo', 'gpt-4-turbo'), | 
				
			|||
        ('gpt-4o', 'gpt-4o'), | 
				
			|||
        ('gpt-4o-mini', 'gpt-4o-mini'), | 
				
			|||
        ('gpt-4-0613', 'gpt-4-0613'), | 
				
			|||
    ]) | 
				
			|||
    openrouter_model_id = fields.Selection(selection='_get_model_selection', | 
				
			|||
                                           string="AI Model") | 
				
			|||
 | 
				
			|||
    @api.model | 
				
			|||
    def default_get(self, fields): | 
				
			|||
        """Override default_get to set a default value for 'max_token' field | 
				
			|||
        when it is included in the fields to fetch.""" | 
				
			|||
        res = super(ResConfigSettings, self).default_get(fields) | 
				
			|||
        if 'max_token' in fields: | 
				
			|||
            res['max_token'] = "4096" | 
				
			|||
        return res | 
				
			|||
 | 
				
			|||
    @api.model | 
				
			|||
    def get_values(self): | 
				
			|||
        """Override get_values to load saved configuration parameters from the system settings. | 
				
			|||
        These values are shown on the settings UI.""" | 
				
			|||
        res = super(ResConfigSettings, self).get_values() | 
				
			|||
        res['generative_ai_systems'] = self.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
            'generative_ai_systems') | 
				
			|||
        res['api_key'] = self.env['ir.config_parameter'].sudo().get_param('api_key') | 
				
			|||
        res['model_id'] = self.env['ir.config_parameter'].sudo().get_param('model_id') | 
				
			|||
        res['openrouter_model_id'] = self.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
            'openrouter_model_id') | 
				
			|||
        res['max_token'] = self.env['ir.config_parameter'].sudo().get_param('max_token') | 
				
			|||
        return res | 
				
			|||
 | 
				
			|||
    @api.onchange('generative_ai_systems') | 
				
			|||
    def _onchange_generative_ai_systems(self): | 
				
			|||
        """Clear model selection when system or API key changes""" | 
				
			|||
        if not self.generative_ai_systems or not self.api_key: | 
				
			|||
            self.model_id = False | 
				
			|||
            self.openrouter_model_id = False | 
				
			|||
 | 
				
			|||
    def _get_model_selection(self): | 
				
			|||
        """Return list of tuples for model selection""" | 
				
			|||
        try: | 
				
			|||
            config_system = self.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                'generative_ai_systems') | 
				
			|||
            config_key = self.env['ir.config_parameter'].sudo().get_param('api_key') | 
				
			|||
            ai_system = self.generative_ai_systems or config_system | 
				
			|||
            api_key = self.api_key or config_key | 
				
			|||
            if ai_system and api_key: | 
				
			|||
                if ai_system == 'openrouter': | 
				
			|||
                    url = "https://openrouter.ai/api/v1/models" | 
				
			|||
                    # Required headers for OpenRouter API | 
				
			|||
                    headers = { | 
				
			|||
                        'Authorization': f'Bearer {api_key}', | 
				
			|||
                        'HTTP-Referer': self.env['ir.config_parameter'].sudo().get_param( | 
				
			|||
                            'web.base.url', ''), | 
				
			|||
                        'X-Title': 'Odoo Integration' | 
				
			|||
                    } | 
				
			|||
                    response = requests.get(url, headers=headers, timeout=10) | 
				
			|||
                    if response.status_code == 200: | 
				
			|||
                        models = response.json().get('data', | 
				
			|||
                                                     [])  # OpenRouter returns models in 'data' field | 
				
			|||
                        if models: | 
				
			|||
                            model_list = [(str(model['id']), str(model['name'])) for model | 
				
			|||
                                          in models] | 
				
			|||
                            return model_list | 
				
			|||
                        else: | 
				
			|||
                            return [('none', 'No Models Available')] | 
				
			|||
                    else: | 
				
			|||
                        return [('none', f'API Error: {response.status_code}')] | 
				
			|||
        except requests.exceptions.RequestException: | 
				
			|||
            return [('none', 'Connection Error')] | 
				
			|||
        except Exception: | 
				
			|||
            return [('none', 'Error Loading Models')] | 
				
			|||
        return [('none', 'No Models Available')] | 
				
			|||
 | 
				
			|||
    def set_values(self): | 
				
			|||
        """Save settings and fetch models""" | 
				
			|||
        super(ResConfigSettings, self).set_values() | 
				
			|||
        self.env['ir.config_parameter'].sudo().set_param('generative_ai_systems', | 
				
			|||
                                                         self.generative_ai_systems) | 
				
			|||
        self.env['ir.config_parameter'].sudo().set_param('api_key', self.api_key) | 
				
			|||
        self.env['ir.config_parameter'].sudo().set_param('max_token', self.max_token) | 
				
			|||
        if self.generative_ai_systems == 'openrouter': | 
				
			|||
            self.env['ir.config_parameter'].sudo().set_param('openrouter_model_id', | 
				
			|||
                                                             self.openrouter_model_id) | 
				
			|||
            self.env['ir.config_parameter'].sudo().set_param('model_id', | 
				
			|||
                                                             '') | 
				
			|||
        else: | 
				
			|||
            self.env['ir.config_parameter'].sudo().set_param('model_id', self.model_id) | 
				
			|||
        if self.generative_ai_systems and self.api_key: | 
				
			|||
            models = self._get_model_selection() | 
				
			|||
            if models and models != [('none', 'No Models Available')]: | 
				
			|||
                # Only save model_id if it's in the list of available models | 
				
			|||
                if self.openrouter_model_id and any( | 
				
			|||
                        self.openrouter_model_id == model[0] for model in models): | 
				
			|||
                    self.env['ir.config_parameter'].sudo().set_param( | 
				
			|||
                        'openrouter_model_id', self.openrouter_model_id) | 
				
			|||
                else: | 
				
			|||
                    # If current model_id is not in available models, set to first available model | 
				
			|||
                    self.env['ir.config_parameter'].sudo().set_param( | 
				
			|||
                        'openrouter_model_id', models[0][0]) | 
				
			|||
                    self.openrouter_model_id = models[0][0] | 
				
			|||
            else: | 
				
			|||
                self.env['ir.config_parameter'].sudo().set_param('openrouter_model_id', '') | 
				
			|||
                self.openrouter_model_id = False | 
				
			|||
@ -0,0 +1,37 @@ | 
				
			|||
# -*- coding: utf-8 -*- | 
				
			|||
############################################################################# | 
				
			|||
# | 
				
			|||
#    Cybrosys Technologies Pvt. Ltd. | 
				
			|||
# | 
				
			|||
#    Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). | 
				
			|||
#    Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) | 
				
			|||
# | 
				
			|||
#    You can modify it under the terms of the GNU AFFERO | 
				
			|||
#    GENERAL PUBLIC LICENSE (AGPL v3), Version 3. | 
				
			|||
# | 
				
			|||
#    This program is distributed in the hope that it will be useful, | 
				
			|||
#    but WITHOUT ANY WARRANTY; without even the implied warranty of | 
				
			|||
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
				
			|||
#    GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. | 
				
			|||
# | 
				
			|||
#    You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE | 
				
			|||
#    (AGPL v3) along with this program. | 
				
			|||
#    If not, see <http://www.gnu.org/licenses/>. | 
				
			|||
# | 
				
			|||
############################################################################# | 
				
			|||
from markupsafe import Markup | 
				
			|||
from odoo import fields, models | 
				
			|||
 | 
				
			|||
 | 
				
			|||
class WebsiteSnippetData(models.Model): | 
				
			|||
    _name = 'website.snippet.data' | 
				
			|||
    _description = 'AI Generated Website Snippet' | 
				
			|||
 | 
				
			|||
    name = fields.Char(string="Snippet Name", required=True) | 
				
			|||
    content = fields.Text(string="Snippet Content", required=True) | 
				
			|||
    image_url = fields.Char(string="Image URL") | 
				
			|||
    create_date = fields.Datetime(string="Created Date", readonly=True) | 
				
			|||
    is_ai_generated = fields.Boolean(string="AI Generated") | 
				
			|||
 | 
				
			|||
    def get_content(self): | 
				
			|||
        return Markup(self.content) | 
				
			|||
		
		
			
  | 
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
| 
		 After Width: | Height: | Size: 28 KiB  | 
| 
		 After Width: | Height: | Size: 628 KiB  | 
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 After Width: | Height: | Size: 210 KiB  | 
| 
		 After Width: | Height: | Size: 209 KiB  | 
| 
		 After Width: | Height: | Size: 109 KiB  | 
| 
		 After Width: | Height: | Size: 495 B  | 
| 
		 After Width: | Height: | Size: 1.0 KiB  | 
| 
		 After Width: | Height: | Size: 624 B  | 
| 
		 After Width: | Height: | Size: 136 KiB  | 
| 
		 After Width: | Height: | Size: 214 KiB  | 
| 
		 After Width: | Height: | Size: 36 KiB  | 
| 
		 After Width: | Height: | Size: 3.6 KiB  | 
| 
		 After Width: | Height: | Size: 310 B  | 
| 
		 After Width: | Height: | Size: 929 B  | 
| 
		 After Width: | Height: | Size: 1.3 KiB  | 
| 
		 After Width: | Height: | Size: 3.3 KiB  | 
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
| 
		 After Width: | Height: | Size: 17 KiB  | 
| 
		 After Width: | Height: | Size: 542 B  | 
| 
		 After Width: | Height: | Size: 576 B  | 
| 
		 After Width: | Height: | Size: 733 B  | 
| 
		 After Width: | Height: | Size: 4.3 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 4.0 KiB  | 
| 
		 After Width: | Height: | Size: 1.7 KiB  | 
| 
		 After Width: | Height: | Size: 383 KiB  | 
| 
		 After Width: | Height: | Size: 2.2 KiB  | 
| 
		 After Width: | Height: | Size: 911 B  | 
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 600 B  | 
| 
		 After Width: | Height: | Size: 673 B  | 
| 
		 After Width: | Height: | Size: 2.0 KiB  | 
| 
		 After Width: | Height: | Size: 462 B  | 
| 
		 After Width: | Height: | Size: 2.1 KiB  | 
| 
		 After Width: | Height: | Size: 926 B  | 
| 
		 After Width: | Height: | Size: 9.0 KiB  | 
| 
		 After Width: | Height: | Size: 23 KiB  | 
| 
		 After Width: | Height: | Size: 7.0 KiB  | 
| 
		 After Width: | Height: | Size: 878 B  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 653 B  | 
| 
		 After Width: | Height: | Size: 800 B  | 
| 
		 After Width: | Height: | Size: 905 B  | 
| 
		 After Width: | Height: | Size: 189 KiB  | 
| 
		 After Width: | Height: | Size: 4.3 KiB  | 
| 
		 After Width: | Height: | Size: 839 B  | 
| 
		 After Width: | Height: | Size: 1.7 KiB  | 
| 
		 After Width: | Height: | Size: 5.9 KiB  | 
| 
		 After Width: | Height: | Size: 1.6 KiB  | 
| 
		 After Width: | Height: | Size: 34 KiB  | 
| 
		 After Width: | Height: | Size: 26 KiB  | 
| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
		 After Width: | Height: | Size: 23 KiB  | 
| 
		 After Width: | Height: | Size: 1.9 KiB  | 
| 
		 After Width: | Height: | Size: 2.3 KiB  | 
| 
		 After Width: | Height: | Size: 427 B  | 
| 
		 After Width: | Height: | Size: 627 B  | 
| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 988 B  | 
| 
		 After Width: | Height: | Size: 3.7 KiB  | 
| 
		 After Width: | Height: | Size: 5.0 KiB  | 
| 
		 After Width: | Height: | Size: 875 B  | 
| 
		 After Width: | Height: | Size: 1.2 KiB  | 
| 
		 After Width: | Height: | Size: 1.0 MiB  | 
| 
		 After Width: | Height: | Size: 89 KiB  | 
| 
		 After Width: | Height: | Size: 544 KiB  | 
| 
		 After Width: | Height: | Size: 1.0 MiB  | 
| 
		 After Width: | Height: | Size: 94 KiB  | 
| 
		 After Width: | Height: | Size: 91 KiB  | 
| 
		 After Width: | Height: | Size: 176 KiB  | 
| 
		 After Width: | Height: | Size: 486 KiB  | 
| 
		 After Width: | Height: | Size: 529 KiB  | 
| 
		 After Width: | Height: | Size: 240 KiB  | 
| 
		 After Width: | Height: | Size: 202 KiB  | 
| 
		 After Width: | Height: | Size: 216 KiB  | 
| 
		 After Width: | Height: | Size: 880 KiB  | 
| 
		 After Width: | Height: | Size: 728 KiB  | 
| 
		 After Width: | Height: | Size: 372 KiB  | 
| 
		 After Width: | Height: | Size: 44 KiB  | 
@ -0,0 +1,857 @@ | 
				
			|||
<!DOCTYPE html> | 
				
			|||
<html lang="en"> | 
				
			|||
<head> | 
				
			|||
    <meta charset="UTF-8"/> | 
				
			|||
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/> | 
				
			|||
    <title>Generative AI</title> | 
				
			|||
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" | 
				
			|||
          rel="stylesheet"/> | 
				
			|||
    <link rel="preconnect" href="https://fonts.googleapis.com"> | 
				
			|||
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | 
				
			|||
    <link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" | 
				
			|||
          rel="stylesheet"> | 
				
			|||
    <link rel="stylesheet" | 
				
			|||
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css"/> | 
				
			|||
    <link rel="stylesheet" | 
				
			|||
          href="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css"/> | 
				
			|||
    <style> | 
				
			|||
        :root { | 
				
			|||
            --primary-color: #7f54b3; | 
				
			|||
            --bg-white: #fff; | 
				
			|||
            --text-color: #121212; | 
				
			|||
            --text-color-light: #64728f; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        body { | 
				
			|||
            font-family: "Montserrat", sans-serif; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .nav-tabs .nav-item.show .nav-link, .nav-tabs .nav-link.active { | 
				
			|||
            color: #121212; | 
				
			|||
            font-family: Montserrat; | 
				
			|||
            font-size: 16px !important; | 
				
			|||
            font-weight: 500 !important; | 
				
			|||
            border-radius: 30px; | 
				
			|||
            line-height: normal; | 
				
			|||
            text-transform: capitalize; | 
				
			|||
            background-color: #F5F5F5; | 
				
			|||
            border: none; | 
				
			|||
            margin-bottom: 0; | 
				
			|||
            padding: 12px 24px; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { | 
				
			|||
            border-color: transparent; | 
				
			|||
            isolation: isolate; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .nav-tabs .nav-link:focus-visible { | 
				
			|||
            border-color: transparent; | 
				
			|||
            box-shadow: none; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        /* owl-carosel */ | 
				
			|||
        .owl-carousel .owl-nav { | 
				
			|||
            position: absolute; | 
				
			|||
            top: 42%; | 
				
			|||
            width: 100%; | 
				
			|||
            display: flex; | 
				
			|||
            justify-content: space-between; | 
				
			|||
            transform: translateY(-42%); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .owl-carousel .owl-nav button.owl-prev { | 
				
			|||
            position: absolute; | 
				
			|||
            right: -36px; | 
				
			|||
            font-size: 28px; | 
				
			|||
            background-color: #e4e4e4; | 
				
			|||
            border-radius: 20px; | 
				
			|||
            width: 40px; | 
				
			|||
            height: 40px; | 
				
			|||
            display: flex; | 
				
			|||
            justify-content: center; | 
				
			|||
            align-items: center; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        .owl-carousel .owl-nav button.owl-next { | 
				
			|||
            position: absolute; | 
				
			|||
            left: -36px; | 
				
			|||
            font-size: 28px; | 
				
			|||
            background-color: #e4e4e4; | 
				
			|||
            border-radius: 20px; | 
				
			|||
            width: 40px; | 
				
			|||
            height: 40px; | 
				
			|||
            display: flex; | 
				
			|||
            justify-content: center; | 
				
			|||
            align-items: center; | 
				
			|||
 | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    </style> | 
				
			|||
</head> | 
				
			|||
<body> | 
				
			|||
<!-- overview --> | 
				
			|||
<div class="container"> | 
				
			|||
    <div class="my-5"> | 
				
			|||
        <!-- button tab --> | 
				
			|||
        <!--  --> | 
				
			|||
        <!-- version support --> | 
				
			|||
        <div class="my-3 d-flex align-items-center justify-content-end"> | 
				
			|||
            <div class="text-center" | 
				
			|||
                 style="background-color:#017E84 !important; font-size:0.8rem !important; color:#fff !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width:120px !important"> | 
				
			|||
                Community | 
				
			|||
            </div> | 
				
			|||
            <div class="text-center" | 
				
			|||
                 style="background-color:#875A7B !important; color:#fff !important; font-size:0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width:120px !important"> | 
				
			|||
                Enterprise | 
				
			|||
            </div> | 
				
			|||
            <div class="text-center" | 
				
			|||
                 style="background-color:#7C7BAD !important; color:#fff !important; font-size:0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width:120px !important"> | 
				
			|||
                Odoo.sh | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
        <div class="tab-content" id="myTabContent"> | 
				
			|||
            <!-- description --> | 
				
			|||
            <div class="tab-pane fade show active" id="home" role="tabpanel" | 
				
			|||
                 aria-labelledby="home-tab"> | 
				
			|||
                <div class="position-relative" | 
				
			|||
                     style="border-radius: 16px; background: #f8f8f8; padding: 20px 0;"> | 
				
			|||
                    <div class="row " style=" | 
				
			|||
          padding: 2rem 0rem 0 !important; | 
				
			|||
        "> | 
				
			|||
                        <div class="col-lg-8 mx-auto gap-4 d-flex flex-column align-items-center"> | 
				
			|||
                            <p class="my-1 text-center text-uppercase" | 
				
			|||
                               style=" | 
				
			|||
              letter-spacing: 4px !important; | 
				
			|||
              color: #7f54b3; | 
				
			|||
              font-weight: bold; | 
				
			|||
              text-align: center; | 
				
			|||
              font-size: 14px; | 
				
			|||
              font-weight: 600; | 
				
			|||
              line-height: 15.96px; | 
				
			|||
              text-transform: uppercase; | 
				
			|||
            "> | 
				
			|||
                                 Generates responsive website snippets from text prompts using AI. | 
				
			|||
                            </p> | 
				
			|||
                            <h1 class="text-center text-uppercase my-0" | 
				
			|||
                                style=" | 
				
			|||
              color: #121212; | 
				
			|||
              font-size: 46px; | 
				
			|||
              font-weight: 700; | 
				
			|||
              line-height: normal; | 
				
			|||
            ">Generative AI</span> | 
				
			|||
                            </h1> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-12 d-flex justify-content-center align-items-center" | 
				
			|||
                             style="margin: 3rem 0;"> | 
				
			|||
                            <img src="./assets/icons/brand-pair.svg" | 
				
			|||
                                 width="100%" | 
				
			|||
                                 height="auto" | 
				
			|||
                                 style="width: 50%" | 
				
			|||
                                 class="img-responsive"/> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-md-12 text-center"> | 
				
			|||
                            <a href="mailto:odoo@cybrosys.com" | 
				
			|||
                               target="_blank" | 
				
			|||
                               style="background-color: transparent;border-radius: 35px; | 
				
			|||
              font-family: Montserrat; | 
				
			|||
              display: inline-block; | 
				
			|||
              padding: 7px 33px; | 
				
			|||
              border: 1px solid #7f54b3; | 
				
			|||
              color: #7f54b3; | 
				
			|||
              text-decoration: none; | 
				
			|||
            " | 
				
			|||
                               class="mx-1 mb-2 deep-1 deep_hover"> | 
				
			|||
                                <img class="img" | 
				
			|||
                                     style="width: 24px" | 
				
			|||
                                     src="./assets/icons/mail.svg"/> | 
				
			|||
                                <span | 
				
			|||
                                 class="pl-2" | 
				
			|||
                                      style=" font-size: 16px; vertical-align: middle" | 
				
			|||
                                >Email Us</span | 
				
			|||
                                > | 
				
			|||
                            </a> | 
				
			|||
                            <a href="skype:cybroopenerp?chat" | 
				
			|||
                               target="_blank" | 
				
			|||
                               style=" | 
				
			|||
              background-color: #7f289b; | 
				
			|||
              font-family: Montserrat; | 
				
			|||
              display: inline-block; | 
				
			|||
              padding: 7px 33px; | 
				
			|||
              border: 1px solid #7f289b; | 
				
			|||
              border-radius: 35px; | 
				
			|||
              text-decoration: none; | 
				
			|||
            " | 
				
			|||
                               class="mx-1 mb-2 deep-1 deep_hover"> | 
				
			|||
                                <img | 
				
			|||
                                        class="img" | 
				
			|||
                                        style="width: 24px" | 
				
			|||
                                        src="./assets/icons/skype-fill.svg" | 
				
			|||
                                /> | 
				
			|||
                                <span | 
				
			|||
                                        class="pl-2" | 
				
			|||
                                        style="color: #fff; font-size: 16px; vertical-align: middle" | 
				
			|||
                                >Skype Us</span | 
				
			|||
                                > | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
<!--                        <div class="d-flex justify-content-center mt-2">--> | 
				
			|||
<!--                            <img src="./assets/icons/hero.gif"--> | 
				
			|||
<!--                                 class="w-100"--> | 
				
			|||
<!--                                 style="z-index: 3; height: auto;">--> | 
				
			|||
<!--                        </div>--> | 
				
			|||
                    </div> | 
				
			|||
                    <div class="position-absolute bottom-0" | 
				
			|||
                         style="z-index: 1; width: 100%;"> | 
				
			|||
                        <img src="./assets/icons/banner-bg.svg" | 
				
			|||
                             class="img-fluid w-100"> | 
				
			|||
                    </div> | 
				
			|||
                    <div class="position-absolute bottom-0 end-0" | 
				
			|||
                         style=" z-index: 2;"> | 
				
			|||
                        <img src="./assets/icons/patter.svg"> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                <!-- key-highlight --> | 
				
			|||
                <div class="" style="border-radius: 16px; | 
				
			|||
     padding: 60px 40px; | 
				
			|||
     border: 1px solid #EBEEF2; | 
				
			|||
     background: #F5F5F7; | 
				
			|||
     box-shadow: 0px 5px 20px -11px rgba(0, 0, 0, 0.25); "> | 
				
			|||
                    <div class="row"> | 
				
			|||
                        <div class="col-lg-12 d-flex flex-column justify-content-center align-items-center"> | 
				
			|||
                            <h2 style=" color: #121212; | 
				
			|||
            text-align: center; | 
				
			|||
            font-size: 40px; | 
				
			|||
            font-weight: 700; | 
				
			|||
            text-transform: uppercase; padding-bottom: 50px;">Key | 
				
			|||
                                Highlights</h2> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-4"> | 
				
			|||
                            <div class="mb-4 d-flex flex-column justify-content-center gap-3" | 
				
			|||
                                 style="border-radius: 12px; border: 1px solid  #B6BCCD; | 
				
			|||
            background:  #FFF;padding:32px "> | 
				
			|||
                                <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                     style="background-color:#7847D9 !important; border-radius:8px !important; height:42px; width:42px"> | 
				
			|||
                                    <img src="./assets/icons/feature-icon.svg" | 
				
			|||
                                         class="img-responsive" height="26px" | 
				
			|||
                                         width="26px"> | 
				
			|||
                                </div> | 
				
			|||
                                <h5 class="m-0" | 
				
			|||
                                    style="color:#000 !important; font-weight:bold"> | 
				
			|||
                                    Easy to set up. | 
				
			|||
                                </h5> | 
				
			|||
                                <p class="m-0" | 
				
			|||
                                   style="font-size:0.9rem; color:#64728f; font-size: 16px; font-weight: 400;"> | 
				
			|||
                                  </p> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-4"> | 
				
			|||
                            <div class="mb-4 d-flex flex-column justify-content-center gap-3" | 
				
			|||
                                 style="border-radius: 12px; | 
				
			|||
          border: 1px solid  #B6BCCD; | 
				
			|||
          background:  #FFF;padding:32px "> | 
				
			|||
                                <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                     style="background-color:#7847D9 !important; border-radius:8px !important; height:42px; width:42px"> | 
				
			|||
                                    <img src="./assets/icons/feature-icon.svg" | 
				
			|||
                                         class="img-responsive" height="26px" | 
				
			|||
                                         width="26px"> | 
				
			|||
                                </div> | 
				
			|||
                                <h5 class="m-0" | 
				
			|||
                                    style="color:#000 !important; font-weight:bold"> | 
				
			|||
                                   AI-powered snippet creation. | 
				
			|||
                                </h5> | 
				
			|||
                                <p class="m-0" | 
				
			|||
                                   style="font-size:0.9rem; color:#64728f; font-size: 16px; font-weight: 400;"> | 
				
			|||
                                </p> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-4"> | 
				
			|||
                            <div class="mb-4 d-flex flex-column justify-content-center gap-3" | 
				
			|||
                                 style="border-radius: 12px; | 
				
			|||
          border: 1px solid  #B6BCCD; | 
				
			|||
          background:  #FFF;padding:32px "> | 
				
			|||
                                <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                     style="background-color:#7847D9 !important; border-radius:8px !important; height:42px; width:42px"> | 
				
			|||
                                    <img src="./assets/icons/feature-icon.svg" | 
				
			|||
                                         class="img-responsive" height="26px" | 
				
			|||
                                         width="26px"> | 
				
			|||
                                </div> | 
				
			|||
                                <h5 class="m-0" | 
				
			|||
                                    style="color:#000 !important; font-weight:bold"> | 
				
			|||
                                   Unique preview & styling. | 
				
			|||
                                </h5> | 
				
			|||
                                <p class="m-0" | 
				
			|||
                                   style="font-size:0.9rem; color:#64728f; font-size: 16px; font-weight: 400;"> | 
				
			|||
 | 
				
			|||
                                </p> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
 | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                <!--code  --> | 
				
			|||
                <div class="my-5"> | 
				
			|||
                    <div class="position-relative" style=" padding: 5rem 4rem 5rem 4rem; background-color: #0A1425; border-radius: 12px;"> | 
				
			|||
                        <div class="d-flex flex-column gap-4"> | 
				
			|||
                            <span class="wrapper-subtitle" | 
				
			|||
                                  style="font-size: 40px; font-weight: 700; color: #fff;line-height: 60px; text-transform: capitalize; width: 450px;  font-family: Montserrat;">Generative AI</span> | 
				
			|||
                            <h3 class="wrapper-details" | 
				
			|||
                                style="font-size: 20px; font-weight: 400; color: #fff; line-height: 32px;  "> | 
				
			|||
                                Are you ready to make your business more | 
				
			|||
                                organized? | 
				
			|||
                                <br> Improve now! | 
				
			|||
                            </h3> | 
				
			|||
                            <div class="d-flex gap-3"> | 
				
			|||
                                <a href="mailto:odoo@cybrosys.com" | 
				
			|||
                                   class="shop-btn" style="cursor: pointer; border-radius: 16px; display: flex; justify-content: center; align-items: center; gap: 7px; | 
				
			|||
            border: 1px solid  #ffffff33; | 
				
			|||
            background-color: #ffffff14; | 
				
			|||
            backdrop-filter: blur(10px); color: #fff; padding: 12px 16px 12px 16px; text-decoration: none;"> | 
				
			|||
             <span style="border-radius: 12px; | 
				
			|||
             background-color: #ffffff1a; | 
				
			|||
             backdrop-filter: blur(6px);padding: 12px;  "> | 
				
			|||
              <img src="./assets/icons/banner-mail.svg"> | 
				
			|||
           </span> | 
				
			|||
                                    <span style="font-weight: 500;font-family: Montserrat;">odoo@cybrosys.com</span> | 
				
			|||
                                </a> | 
				
			|||
                                <a href="tel:+91 9074270811" class="shop-btn" | 
				
			|||
                                   style="cursor: pointer; border-radius: 16px; display: flex; justify-content: center; align-items: center; gap: 7px; | 
				
			|||
             border: 1px solid  #ffffff33; | 
				
			|||
             background-color: #ffffff14; | 
				
			|||
            backdrop-filter: blur(10px); color: #fff; padding: 12px 22px 12px 18px; text-decoration: none;"> | 
				
			|||
             <span style="border-radius: 12px; | 
				
			|||
             background-color: #ffffff1a; | 
				
			|||
             backdrop-filter: blur(6px);padding: 12px;"> | 
				
			|||
              <img src="./assets/icons/banner-call.svg"> | 
				
			|||
           </span> | 
				
			|||
                                    <span style="font-weight: 500;font-family: Montserrat;">+91 9074270811</span> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="position-absolute bottom-0 end-0"> | 
				
			|||
                            <img src="./assets/icons/banner-pattern.svg" | 
				
			|||
                                 style="width: 540px;"> | 
				
			|||
                        </div> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                <!-- end-code --> | 
				
			|||
                <!--  --> | 
				
			|||
                <!-- screenshot and other --> | 
				
			|||
 | 
				
			|||
                <div class="mb-4 bg-white" | 
				
			|||
                     style=" border: 1px solid #EBEEF2; border-radius: 6px; box-shadow: 0px 8px 20px -4px rgba(0, 0, 0, 0.10); border: 1px solid #EBEEF2;"> | 
				
			|||
                    <div> | 
				
			|||
                        <ul class="nav nav-tabs justify-content-center bg-white py-2" | 
				
			|||
                            id="myTab" role="tablist" | 
				
			|||
                            style="border-radius: 6px 6px 0 0;"> | 
				
			|||
                            <li class="nav-item"> | 
				
			|||
                                <a aria-controls="overview" | 
				
			|||
                                   aria-bs-selected="true" | 
				
			|||
                                   class="nav-link active" data-bs-toggle="tab" | 
				
			|||
                                   href="#overview" id="overview-tab" role="tab" | 
				
			|||
                                   style="color:#121212; font-weight:500; font-size:16px"> | 
				
			|||
                                    Screenshots</a> | 
				
			|||
                            </li> | 
				
			|||
                            <li class="nav-item"> | 
				
			|||
                                <a aria-controls="feature" | 
				
			|||
                                   aria-bs-selected="false" | 
				
			|||
                                   class="nav-link py-2" data-bs-toggle="tab" | 
				
			|||
                                   href="#feature" id="feature-tab" role="tab" | 
				
			|||
                                   style="color:#121212; font-weight:500; font-size:16px">Features</a> | 
				
			|||
                            </li> | 
				
			|||
                        </ul> | 
				
			|||
                    </div> | 
				
			|||
                    <div class="tab-content p-md-5 p-2 py-3" id="myTabContent"> | 
				
			|||
                        <div aria-labelledby="overview-tab" | 
				
			|||
                             class="tab-pane fade show active" id="overview" | 
				
			|||
                             role="tabpanel"> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px"> | 
				
			|||
                                <img alt="acc_bg" | 
				
			|||
                                     class="w-100 h-100 position-absolute img-fluid left_0" | 
				
			|||
                                     loading="lazy" | 
				
			|||
                                     src="//apps.odoocdn.com/apps/assets/17.0/ks_dashboard_ninja/ai-img/o3.png?007008f" | 
				
			|||
                                     style=""> | 
				
			|||
                            </div> | 
				
			|||
                            <!-- screenshots section--> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px; background-color:#f4f4f4"> | 
				
			|||
                                <div class="p-md-5 p-3 position-relative"> | 
				
			|||
                                    <div class="row"> | 
				
			|||
                                        <div class="col-md-12"> | 
				
			|||
                                            <h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px; | 
				
			|||
                              font-weight: 700;"> | 
				
			|||
                                      <span style="color:#121212; font-size:calc(1.1rem + 1vw)"> Navigate to the | 
				
			|||
                                      </span> | 
				
			|||
                                                <span style="color: #7f54b3; font-size:calc(1.1rem + 1vw)">AI tools section.</span> | 
				
			|||
                                            </h1> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 mb-4"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                                In Settings → Website → Configuration → AI tools section, | 
				
			|||
                                                select your AI system (OpenAI or OpenRouter), enter your API key, | 
				
			|||
                                                set maximum tokens and choose your preferred AI model. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot1.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px; background-color:#f4f4f4"> | 
				
			|||
                                <div class="p-md-5 p-3 position-relative"> | 
				
			|||
                                    <div class="row"> | 
				
			|||
                                        <div class="col-md-12"> | 
				
			|||
                                            <h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px; | 
				
			|||
                              font-weight: 700;"> | 
				
			|||
                                      <span style="color:#121212; font-size:calc(1.1rem + 1vw)">Click | 
				
			|||
                                      </span> | 
				
			|||
                                                <span style="color: #7f54b3; font-size:calc(1.1rem + 1vw)"> Edit Button.</span> | 
				
			|||
                                            </h1> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 mb-4"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                                Click the “Edit” button to open the Website Editor Sidebar. | 
				
			|||
 | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot2.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px; background-color:#f4f4f4"> | 
				
			|||
                                <div class="p-md-5 p-3 position-relative"> | 
				
			|||
                                    <div class="row"> | 
				
			|||
                                        <div class="col-md-12"> | 
				
			|||
                                            <h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px; | 
				
			|||
                              font-weight: 700;"> | 
				
			|||
                                      <span style="color:#121212; font-size:calc(1.1rem + 1vw)">Open | 
				
			|||
                                      </span> | 
				
			|||
                                                <span style="color: #7f54b3; font-size:calc(1.1rem + 1vw)">Add Snippet Dialog. </span> | 
				
			|||
                                            </h1> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 mb-4"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                                 Click AI Generated to view the Add Snippet Dialog. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot3.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px; background-color:#f4f4f4"> | 
				
			|||
                                <div class="p-md-5 p-3 position-relative"> | 
				
			|||
                                    <div class="row"> | 
				
			|||
                                        <div class="col-md-12"> | 
				
			|||
                                            <h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px; | 
				
			|||
                              font-weight: 700;"> | 
				
			|||
                                      <span style="color:#121212; font-size:calc(1.1rem + 1vw)">Click | 
				
			|||
 | 
				
			|||
                                      </span> | 
				
			|||
                                                <span style="color:#7f54b3; font-size:calc(1.1rem + 1vw)">Generate with AI.</span> | 
				
			|||
                                            </h1> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 mb-4"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                                 Click Generate with AI to open the Generate Snippet with AI dialog box. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot4.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="position-relative mb-4" | 
				
			|||
                                 style="border-radius:10px; background-color:#f4f4f4"> | 
				
			|||
                                <div class="p-md-5 p-3 position-relative"> | 
				
			|||
                                    <div class="row"> | 
				
			|||
                                        <div class="col-md-12"> | 
				
			|||
                                            <h1 style="font-weight:bold; font-size:calc(1.1rem + 1vw); line-height:120%; text-align:center; text-transform:capitalize; font-size: 40px; | 
				
			|||
                              font-weight: 700;"> | 
				
			|||
                                      <span style="color:#121212; font-size:calc(1.1rem + 1vw)">Generate | 
				
			|||
 | 
				
			|||
                                      </span> | 
				
			|||
                                                <span style="color:#7f54b3; font-size:calc(1.1rem + 1vw)">Snippet.</span> | 
				
			|||
                                            </h1> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 mb-4"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                               Enter the snippet name and prompt, and click Generate. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot5.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                            <div class="col-md-12 mb-4 pt-5"> | 
				
			|||
                                            <p style="font-weight:400; font-size:16px; line-height:150%; text-align:center;  color:#64728f"> | 
				
			|||
                                                You can view the newly created snippet in the Add Snippet dialog box. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
                                        <div class="col-md-12 text-center"> | 
				
			|||
                                            <div class="d-inline-block p-3 shadow-sm" | 
				
			|||
                                                 style="background-color:#fff; border-radius:10px"> | 
				
			|||
                                                <img alt="" class="img-fluid" | 
				
			|||
                                                     loading="lazy" | 
				
			|||
                                                     src="./assets/screenshots/Screenshot6.png" | 
				
			|||
                                                     style="min-height: 1px;"> | 
				
			|||
                                            </div> | 
				
			|||
                                        </div> | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                        <div aria-labelledby="feature-tab" | 
				
			|||
                             class="tab-pane fade show py-1" id="feature" | 
				
			|||
                             role="tabpanel"> | 
				
			|||
                            <div class="row py-4"> | 
				
			|||
                                <!-- Features Section --> | 
				
			|||
                                <div class="col-md-6 col-sm-12 p-3"> | 
				
			|||
                                    <div class="d-flex flex-column align-items-start h-100" | 
				
			|||
                                         style="padding:30px; border-radius:12px; background-color:#faf8ff"> | 
				
			|||
                                        <div class="d-flex align-items-center justify-content-center"> | 
				
			|||
                                            <div class="d-flex align-items-center justify-content-center " | 
				
			|||
                                                 style="width:36px; height:36px; border-radius:50%; background-color:#7847D9 ; margin-right:10px"> | 
				
			|||
                                                <i class="fa fa-star  " | 
				
			|||
                                                   style="color:#fff; font-size:14px"></i> | 
				
			|||
                                            </div> | 
				
			|||
                                            <p style="color:#1A202C; font-weight:600; font-size:1.2rem; margin-bottom:2px"> | 
				
			|||
                                                  Instantly generate custom website snippets from text prompts using | 
				
			|||
                                                OpenAI or OpenRouter. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
 | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                                <div class="col-md-6 col-sm-12 p-3"> | 
				
			|||
                                    <div class="d-flex flex-column align-items-start h-100" | 
				
			|||
                                         style="padding:30px; border-radius:12px; background-color:#faf8ff"> | 
				
			|||
                                        <div class="d-flex align-items-center justify-content-center"> | 
				
			|||
                                            <div class="d-flex align-items-center justify-content-center " | 
				
			|||
                                                 style="width:36px; height:36px; border-radius:50%; background-color:#7847D9 ; margin-right:10px"> | 
				
			|||
                                                <i class="fa fa-star  " | 
				
			|||
                                                   style="color:#fff; font-size:14px"></i> | 
				
			|||
                                            </div> | 
				
			|||
                                            <p style="color:#1A202C; font-weight:600; font-size:1.2rem; margin-bottom:2px"> | 
				
			|||
                                                 Create, preview, and insert AI-generated snippets directly within | 
				
			|||
                                                Odoo’s website editor. | 
				
			|||
                                            </p> | 
				
			|||
                                        </div> | 
				
			|||
 | 
				
			|||
                                    </div> | 
				
			|||
                                </div> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                <!--  --> | 
				
			|||
                <!-- related post --> | 
				
			|||
                <!--  --> | 
				
			|||
                <section class="oe_container mt32"> | 
				
			|||
                <h2 style="color: #091E42;font-family: "Montserrat";text-align: center;margin: 25px auto;text-transform: uppercase;" class="oe_slogan"> | 
				
			|||
                    <b>Related Products</b> | 
				
			|||
                </h2> | 
				
			|||
                <div id="demo" class="row carousel slide mt64 mb32" data-bs-ride="carousel"> | 
				
			|||
                    <!-- The slideshow --> | 
				
			|||
                    <div class="carousel-inner"> | 
				
			|||
                        <div class="carousel-item active"> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a href="https://apps.odoo.com/apps/modules/18.0/hide_menu_user" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px; padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b1.png" /> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                            Hide Any Menu User Wise</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a href="https://apps.odoo.com/apps/modules/18.0/web_login_styles" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px; padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b2.png" /> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                             Customize Login Page Style</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a  href="https://apps.odoo.com/apps/modules/18.0/whatsapp_mail_messaging" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px;padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b3.png"/> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                            Odoo Whatsapp Connector</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="carousel-item"> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a href="https://apps.odoo.com/apps/modules/18.0/login_user_detail" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px; padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b4.png" /> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                            User Log Details</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a href="https://apps.odoo.com/apps/modules/18.0/report_attachment_preview" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px; padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b5.png" /> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                            Reports and Attachments Preview in Browser</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                            <div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" style="float: left; padding: 10px;"> | 
				
			|||
                                <a href="https://apps.odoo.com/apps/modules/18.0/project_dashboard_odoo" target="_blank" style="color: #000; text-decoration: none;"> | 
				
			|||
                                    <div style="border-radius: 6px; padding: 16px; border: 1px solid #cbcbcb;" class="shadow-sm"> | 
				
			|||
                                        <img class="img img-responsive center-block" style=" max-width: 100%;" src="./assets/modules/b6.png" /> | 
				
			|||
                                        <h4 class="mt0 text-truncate" style="text-align:center;width:100% margin-bottom: 8px; font-weight: 600; padding-top: 16px; text-decoration:none;font-size: 18px; padding-bottom: 8px; margin-bottom: 0px"> | 
				
			|||
                                           Project Dashboard</h4> | 
				
			|||
                                    </div> | 
				
			|||
                                </a> | 
				
			|||
                            </div> | 
				
			|||
                        </div> | 
				
			|||
                    </div> | 
				
			|||
                    <!-- Left and right controls --> | 
				
			|||
                    <a class="carousel-control-prev" href="#demo" data-bs-slide="prev" style="margin-left: -30px;width: 35px;color: #000;"> | 
				
			|||
                        <span class="carousel-control-prev-icon"> | 
				
			|||
        <i class="fa fa-chevron-left" style="font-size:24px"></i> | 
				
			|||
      </span> | 
				
			|||
                    </a> | 
				
			|||
                    <a class="carousel-control-next" href="#demo" data-bs-slide="next" style="margin-right: -30px;width: 35px;color: #000;"> | 
				
			|||
                        <span class="carousel-control-next-icon"> | 
				
			|||
        <i class="fa fa-chevron-right" style="font-size:24px"></i> | 
				
			|||
      </span> | 
				
			|||
                    </a> | 
				
			|||
                </div> | 
				
			|||
            </section> | 
				
			|||
                <!-- service-section --> | 
				
			|||
 | 
				
			|||
                <section id="services" class="mt-5" style="border-radius: 16px; | 
				
			|||
                                                            border: 1px solid #EBEEF2; | 
				
			|||
                                                            background: var(--Neutral-N0, #FFF); | 
				
			|||
                                                            padding: 60px 40px; | 
				
			|||
                                                            box-shadow: 0px 5px 20px -11px rgba(0, 0, 0, 0.25);"> | 
				
			|||
                    <div class="text-center mt-4"><h3 class="mb-0" style="color: #000; | 
				
			|||
                                                                  text-align: center; | 
				
			|||
                                                                  font-family: Montserrat; | 
				
			|||
                                                                  font-size: 40px; | 
				
			|||
                                                                  font-style: normal; | 
				
			|||
                                                                  font-weight: 700; | 
				
			|||
                                                                  line-height: normal; | 
				
			|||
                                                                  text-transform: uppercase; | 
				
			|||
                                                                  padding-bottom: 50px;"> | 
				
			|||
                        Our Services</h3></div> | 
				
			|||
                    <div class="row mt-3"> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#FFE2E5; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#FA5A7D; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/gear.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
                                  color: var(--text-color); | 
				
			|||
                                  font-weight: 600;"> Odoo Customization</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#FFF4DE; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#FF947A; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/wrench-icon.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
                    color: var(--text-color); | 
				
			|||
                    font-weight: 600;"> Odoo Implementation</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#DCFCE7; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#3CD856; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/life-ring-icon.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
                  color: var(--text-color); | 
				
			|||
                  font-weight: 600;">Odoo Support</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#F3E8FF; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#BF83FF; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/arrows-repeat.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
                color: var(--text-color); | 
				
			|||
                font-weight: 600;">Odoo Migration</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#F1F9FF; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#01649C; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/puzzle-piece-icon.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
              color: var(--text-color); | 
				
			|||
              font-weight: 600;">Odoo integration</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#EDF8ED; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
 | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#69CC70; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/odoo-consultancy.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
            color: var(--text-color); | 
				
			|||
            font-weight: 600;">Odoo Consultancy</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#F1F6FF; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
 | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#2E4556; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/odoo-licencing.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
          color: var(--text-color); | 
				
			|||
          font-weight: 600;">Odoo Licensing</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                        <div class="col-lg-3 col-sm-12 mb-3"> | 
				
			|||
                            <a href="#" style="text-decoration:none"> | 
				
			|||
                                <div class="btn-lg btn-block p-4 mb-2 d-flex flex-column justify-content-center align-items-center" | 
				
			|||
                                     style="font-size:25px; font-weight:bold;background-color:#FAF6EA; margin:auto; gap: 16px; border-radius: 8px;"> | 
				
			|||
 | 
				
			|||
                                    <div class="d-flex justify-content-center align-items-center" | 
				
			|||
                                         style="background-color:#FCD12C; border-radius:50%; height:56px; width:56px"> | 
				
			|||
                                        <img src="./assets/icons/hire-odoo.svg" | 
				
			|||
                                             class="img-responsive" | 
				
			|||
                                             height="28px" width="28px"> | 
				
			|||
                                    </div> | 
				
			|||
                                    <span style="font-size: 18px; | 
				
			|||
          color: var(--text-color); | 
				
			|||
          font-weight: 600;">Hire Odoo Developer</span> | 
				
			|||
                                </div> | 
				
			|||
                            </a> | 
				
			|||
                        </div> | 
				
			|||
                    </div> | 
				
			|||
            </div> | 
				
			|||
 | 
				
			|||
            <!-- licence --> | 
				
			|||
            <div class="tab-pane fade" id="profile" role="tabpanel" | 
				
			|||
                 aria-labelledby="profile-tab"> | 
				
			|||
                <div class="px-5"> | 
				
			|||
                    .... | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
            <!--  --> | 
				
			|||
        </div> | 
				
			|||
        </section> | 
				
			|||
        <!--  --> | 
				
			|||
    </div> | 
				
			|||
</div> | 
				
			|||
</body> | 
				
			|||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script> | 
				
			|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" | 
				
			|||
        integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" | 
				
			|||
        crossorigin="anonymous" referrerpolicy="no-referrer"></script> | 
				
			|||
<script src="https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js"></script> | 
				
			|||
<script> | 
				
			|||
    $('.owl-carousel').owlCarousel({ | 
				
			|||
        rtl: true, | 
				
			|||
        loop: true, | 
				
			|||
        margin: 10, | 
				
			|||
        nav: true, | 
				
			|||
        responsive: { | 
				
			|||
            0: { | 
				
			|||
                items: 1 | 
				
			|||
            }, | 
				
			|||
            600: { | 
				
			|||
                items: 3 | 
				
			|||
            }, | 
				
			|||
            1000: { | 
				
			|||
                items: 3 | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    }) | 
				
			|||
</script> | 
				
			|||
</html> | 
				
			|||
@ -0,0 +1,3 @@ | 
				
			|||
.s_ai_snippet .loading-spinner { | 
				
			|||
    min-height: 100px; | 
				
			|||
} | 
				
			|||
| 
		 After Width: | Height: | Size: 12 KiB  | 
@ -0,0 +1,117 @@ | 
				
			|||
/** @odoo-module **/ | 
				
			|||
 | 
				
			|||
import { patch } from "@web/core/utils/patch"; | 
				
			|||
import { useService } from "@web/core/utils/hooks"; | 
				
			|||
import { rpc } from "@web/core/network/rpc"; | 
				
			|||
import { Component, xml, useRef, useState } from "@odoo/owl"; | 
				
			|||
import { AddSnippetDialog } from "@web_editor/js/editor/add_snippet_dialog"; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
// Define a custom Owl component for the AI Prompt Modal
 | 
				
			|||
class AIPromptModal extends Component { | 
				
			|||
    setup() { | 
				
			|||
        this.orm = useService("orm"); | 
				
			|||
        this.notification = useService("notification"); | 
				
			|||
        this.textAreaRef = useRef('textAreaRef'); | 
				
			|||
        this.nameRef = useRef('snippetNameRef'); | 
				
			|||
        this.state = useState({ | 
				
			|||
            isLoading: false, | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
    // Close the modal (disabled if a request is loading)
 | 
				
			|||
    onClose() { | 
				
			|||
        if (this.state.isLoading) { | 
				
			|||
            return; | 
				
			|||
        } | 
				
			|||
        this.props.close(); | 
				
			|||
    } | 
				
			|||
    async onSubmit() { | 
				
			|||
        const promptValue = this.textAreaRef.el.value; | 
				
			|||
        const snippetName = this.nameRef.el.value; | 
				
			|||
 | 
				
			|||
        if (!promptValue || !snippetName) { | 
				
			|||
            this.notification.add( | 
				
			|||
                "Please fill in both the prompt and snippet name", | 
				
			|||
                { type: "warning" } | 
				
			|||
            ); | 
				
			|||
            return; | 
				
			|||
        } | 
				
			|||
        try { | 
				
			|||
            this.state.isLoading = true; | 
				
			|||
            const result = await rpc('/website/generate_snippet', { | 
				
			|||
                prompt: promptValue, | 
				
			|||
                name: snippetName | 
				
			|||
            }); | 
				
			|||
            if (result.error) { | 
				
			|||
                this.notification.add(result.error, { type: "danger" }); | 
				
			|||
            } else { | 
				
			|||
                this.notification.add( | 
				
			|||
                    "Snippet generated successfully! Refreshing snippet panel...", | 
				
			|||
                    { type: "success" } | 
				
			|||
                ); | 
				
			|||
                window.location.reload(); | 
				
			|||
            } | 
				
			|||
        } catch (error) { | 
				
			|||
            this.notification.add( | 
				
			|||
                "Failed to generate snippet: " + error, | 
				
			|||
                { type: "danger" } | 
				
			|||
            ); | 
				
			|||
        } finally { | 
				
			|||
            this.state.isLoading = false; | 
				
			|||
        } | 
				
			|||
        this.props.close(); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
// Define the template for the modal
 | 
				
			|||
AIPromptModal.template = xml` | 
				
			|||
    <div class="modal o_technical_modal d-block" tabindex="-1" role="dialog"> | 
				
			|||
        <div class="modal-dialog" role="document"> | 
				
			|||
            <div class="modal-content"> | 
				
			|||
                <div class="modal-header"> | 
				
			|||
                    <h5 class="modal-title">Generate Snippet with AI</h5> | 
				
			|||
                    <button type="button" class="btn-close" t-att-disabled="state.isLoading" t-on-click="onClose" aria-label="Close"/> | 
				
			|||
                </div> | 
				
			|||
                <div class="modal-body"> | 
				
			|||
                    <div class="form-group mb-3"> | 
				
			|||
                        <label for="snippet-name" class="form-label">Snippet Name</label> | 
				
			|||
                        <input type="text" id="snippet-name" class="form-control" t-ref="snippetNameRef" | 
				
			|||
                               t-att-disabled="state.isLoading" placeholder="Enter a name for your snippet"/> | 
				
			|||
                    </div> | 
				
			|||
                    <div class="form-group"> | 
				
			|||
                        <label for="ai-prompt" class="form-label">Enter your prompt here...</label> | 
				
			|||
                        <textarea id="ai-prompt" class="form-control" rows="3" t-ref="textAreaRef" | 
				
			|||
                                 t-att-disabled="state.isLoading"/> | 
				
			|||
                    </div> | 
				
			|||
                </div> | 
				
			|||
                <div class="modal-footer"> | 
				
			|||
                    <button type="button" class="btn btn-secondary" t-att-disabled="state.isLoading" t-on-click="onClose">Cancel</button> | 
				
			|||
                    <button type="button" class="btn btn-primary" t-att-disabled="state.isLoading" t-on-click="onSubmit"> | 
				
			|||
                        <t t-if="state.isLoading"> | 
				
			|||
                            <span class="fa fa-spinner fa-spin me-1"></span> | 
				
			|||
                            Generating... | 
				
			|||
                        </t> | 
				
			|||
                        <t t-else="">Generate</t> | 
				
			|||
                    </button> | 
				
			|||
                </div> | 
				
			|||
            </div> | 
				
			|||
        </div> | 
				
			|||
    </div> | 
				
			|||
`;
 | 
				
			|||
 | 
				
			|||
// Patch the AddSnippetDialog to add the AI prompt dialog on click
 | 
				
			|||
const originalSetup = AddSnippetDialog.prototype.setup; | 
				
			|||
patch(AddSnippetDialog.prototype, { | 
				
			|||
    setup() { | 
				
			|||
        originalSetup.call(this); | 
				
			|||
        this.dialog = useService("dialog"); | 
				
			|||
    }, | 
				
			|||
    async _onGenerateClick() { | 
				
			|||
        this.dialog.add(AIPromptModal, { | 
				
			|||
            onClose: () => { | 
				
			|||
                // Refresh the snippets panel after closing
 | 
				
			|||
                this.render(); | 
				
			|||
            } | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
@ -0,0 +1,241 @@ | 
				
			|||
/** @odoo-module **/ | 
				
			|||
 | 
				
			|||
import { patch } from "@web/core/utils/patch"; | 
				
			|||
import { _t } from "@web/core/l10n/translation"; | 
				
			|||
import { AddSnippetDialog } from "@web_editor/js/editor/add_snippet_dialog"; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Patch AddSnippetDialog to: | 
				
			|||
 * - add custom tooltip on hover showing snippet name | 
				
			|||
 */ | 
				
			|||
patch(AddSnippetDialog.prototype, { | 
				
			|||
    /** | 
				
			|||
     * Override default snippet insertion. | 
				
			|||
     * Inserts snippets into two columns, filters by search, handles custom snippets, etc. | 
				
			|||
     */ | 
				
			|||
    async insertSnippets() { | 
				
			|||
        const insertSnippetsCallID = ++this.currentInsertSnippetsCallID; | 
				
			|||
        let snippetsToDisplay = [...this.props.snippets.values()].filter(snippet => { | 
				
			|||
            return !snippet.excluded && snippet.group; | 
				
			|||
        }); | 
				
			|||
        if (this.state.search) { | 
				
			|||
            const search = this.state.search; | 
				
			|||
            const selectorSearch = /^s_[\w-]*$/.test(search) && `[class^="${search}"], [class*=" ${search}"]`; | 
				
			|||
            const lowerCasedSearch = search.toLowerCase(); | 
				
			|||
            const strMatches = str => str.toLowerCase().includes(lowerCasedSearch); | 
				
			|||
            snippetsToDisplay = snippetsToDisplay.filter(snippet => { | 
				
			|||
                return selectorSearch && ( | 
				
			|||
                        snippet.baseBody.matches(selectorSearch) | 
				
			|||
                        || snippet.baseBody.querySelector(selectorSearch) | 
				
			|||
                    ) | 
				
			|||
                    || strMatches(snippet.category.text) | 
				
			|||
                    || strMatches(snippet.displayName) | 
				
			|||
                    || strMatches(snippet.data.oeKeywords || ''); | 
				
			|||
            }); | 
				
			|||
            if (selectorSearch) { | 
				
			|||
                snippetsToDisplay.sort((snippetA, snippetB) => { | 
				
			|||
                    if (snippetA.data.snippet === search) { | 
				
			|||
                        return -1; | 
				
			|||
                    } | 
				
			|||
                    if (snippetB.data.snippet === search) { | 
				
			|||
                        return 1; | 
				
			|||
                    } | 
				
			|||
                    const aHasExactClassOnRoot = snippetA.baseBody.classList.contains(search); | 
				
			|||
                    const bHasExactClassOnRoot = snippetB.baseBody.classList.contains(search); | 
				
			|||
                    if (aHasExactClassOnRoot !== bHasExactClassOnRoot) { | 
				
			|||
                        return aHasExactClassOnRoot ? -1 : 1; | 
				
			|||
                    } | 
				
			|||
                    const aHasPartialClassOnRoot = snippetA.baseBody.matches(selectorSearch); | 
				
			|||
                    const bHasPartialClassOnRoot = snippetB.baseBody.matches(selectorSearch); | 
				
			|||
                    if (aHasPartialClassOnRoot !== bHasPartialClassOnRoot) { | 
				
			|||
                        return aHasPartialClassOnRoot ? -1 : 1; | 
				
			|||
                    } | 
				
			|||
 | 
				
			|||
                    return 0; | 
				
			|||
                }); | 
				
			|||
            } | 
				
			|||
        } else { | 
				
			|||
            snippetsToDisplay = snippetsToDisplay.filter(snippet => { | 
				
			|||
                return snippet.group === this.state.groupSelected; | 
				
			|||
            }); | 
				
			|||
        } | 
				
			|||
        this.iframeDocument.body.scrollTop = 0; | 
				
			|||
        const rowEl = document.createElement("div"); | 
				
			|||
        rowEl.classList.add("row", "g-0", "o_snippets_preview_row"); | 
				
			|||
        rowEl.style.setProperty("direction", this.props.frontendDirection); | 
				
			|||
        const leftColEl = document.createElement("div"); | 
				
			|||
        leftColEl.classList.add("col-lg-6"); | 
				
			|||
        rowEl.appendChild(leftColEl); | 
				
			|||
        const rightColEl = document.createElement("div"); | 
				
			|||
        rightColEl.classList.add("col-lg-6"); | 
				
			|||
        rowEl.appendChild(rightColEl); | 
				
			|||
        this.iframeDocument.body.appendChild(rowEl); | 
				
			|||
        const BIG_CHUNK_SIZE = 6; | 
				
			|||
        const SMALL_CHUNK_SIZE = 3; | 
				
			|||
        const chunks = [snippetsToDisplay.splice(0, BIG_CHUNK_SIZE)]; | 
				
			|||
        while (snippetsToDisplay.length) { | 
				
			|||
            chunks.push(snippetsToDisplay.splice(0, SMALL_CHUNK_SIZE)); | 
				
			|||
        } | 
				
			|||
        let leftColSize = 0; | 
				
			|||
        let rightColSize = 0; | 
				
			|||
        for (const chunk of chunks) { | 
				
			|||
            const itemEls = await Promise.all(chunk.map(snippet => { | 
				
			|||
                let containerEl = null; | 
				
			|||
                let clonedSnippetEl; | 
				
			|||
                let originalSnippet; | 
				
			|||
                if (snippet.isCustom) { | 
				
			|||
                    originalSnippet = [...this.props.snippets.values()].filter(snip => | 
				
			|||
                        !snip.isCustom && snip.name === snippet.name | 
				
			|||
                    )[0]; | 
				
			|||
                    if (originalSnippet.baseBody.querySelector(".s_dialog_preview") | 
				
			|||
                        || originalSnippet.imagePreview | 
				
			|||
                        || originalSnippet.name === "s_countdown") { | 
				
			|||
                        clonedSnippetEl = originalSnippet.baseBody.cloneNode(true); | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
                if (!clonedSnippetEl) { | 
				
			|||
                    clonedSnippetEl = snippet.baseBody.cloneNode(true); | 
				
			|||
                } | 
				
			|||
                clonedSnippetEl.classList.remove("oe_snippet_body"); | 
				
			|||
                const snippetPreviewWrapEl = document.createElement("div"); | 
				
			|||
                snippetPreviewWrapEl.classList.add("o_snippet_preview_wrap", "position-relative"); | 
				
			|||
                snippetPreviewWrapEl.dataset.snippetId = snippet.name; | 
				
			|||
                snippetPreviewWrapEl.dataset.snippetKey = snippet.key; | 
				
			|||
                snippetPreviewWrapEl.dataset.snippetName = snippet.displayName; | 
				
			|||
                snippetPreviewWrapEl.appendChild(clonedSnippetEl); | 
				
			|||
                this.__onSnippetPreviewClick = this._onSnippetPreviewClick.bind(this); | 
				
			|||
                snippetPreviewWrapEl.addEventListener("click", this.__onSnippetPreviewClick); | 
				
			|||
                snippetPreviewWrapEl.addEventListener("mouseenter", this.__onSnippetPreviewHover); | 
				
			|||
                containerEl = snippetPreviewWrapEl; | 
				
			|||
                if (snippet.installable) { | 
				
			|||
                    snippetPreviewWrapEl.classList.add("o_snippet_preview_install"); | 
				
			|||
                    clonedSnippetEl.dataset.moduleId = snippet.moduleId; | 
				
			|||
                    const installBtnEl = document.createElement("button"); | 
				
			|||
                    installBtnEl.classList.add("o_snippet_preview_install_btn", "btn", "text-white", "rounded-1", "mx-auto", "p-2", "bottom-50"); | 
				
			|||
                    installBtnEl.innerText = _t("Install %s", snippet.displayName); | 
				
			|||
                    snippetPreviewWrapEl.appendChild(installBtnEl); | 
				
			|||
                } | 
				
			|||
                 // Image preview
 | 
				
			|||
                const imagePreview = snippet.imagePreview || originalSnippet?.imagePreview; | 
				
			|||
                if (imagePreview) { | 
				
			|||
                    clonedSnippetEl.style.setProperty("padding", "0", "important"); | 
				
			|||
                    const previewImgDivEl = document.createElement("div"); | 
				
			|||
                    previewImgDivEl.classList.add("s_dialog_preview", "s_dialog_preview_image"); | 
				
			|||
                    const previewImgEl = document.createElement("img"); | 
				
			|||
                    previewImgEl.src = imagePreview; | 
				
			|||
                    previewImgDivEl.appendChild(previewImgEl); | 
				
			|||
                    clonedSnippetEl.innerHTML = ""; | 
				
			|||
                    clonedSnippetEl.appendChild(previewImgDivEl); | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                clonedSnippetEl.classList.remove("o_dynamic_empty"); | 
				
			|||
 | 
				
			|||
                if (snippet.isCustom) { | 
				
			|||
                    const editCustomSnippetEl = document.createElement("div"); | 
				
			|||
                    editCustomSnippetEl.classList.add("d-grid", "mt-2", "mx-5", "gap-2", | 
				
			|||
                        "d-md-flex", "justify-content-md-end", "o_custom_snippet_edit"); | 
				
			|||
                    const spanEl = document.createElement("span"); | 
				
			|||
                    spanEl.classList.add("w-100"); | 
				
			|||
                    spanEl.textContent = snippet.displayName; | 
				
			|||
                    const renameBtnEl = document.createElement("button"); | 
				
			|||
                    renameBtnEl.classList.add("btn", "fa", "fa-pencil", "me-md-2"); | 
				
			|||
                    renameBtnEl.type = "button"; | 
				
			|||
 | 
				
			|||
                    const removeBtnEl = document.createElement("button"); | 
				
			|||
                    removeBtnEl.classList.add("btn", "fa", "fa-trash"); | 
				
			|||
                    removeBtnEl.type = "button"; | 
				
			|||
 | 
				
			|||
                    editCustomSnippetEl.appendChild(spanEl); | 
				
			|||
                    editCustomSnippetEl.appendChild(renameBtnEl); | 
				
			|||
                    editCustomSnippetEl.appendChild(removeBtnEl); | 
				
			|||
 | 
				
			|||
                    const customSnippetWrapEl = document.createElement("div"); | 
				
			|||
                    customSnippetWrapEl.classList.add("o_custom_snippet_wrap"); | 
				
			|||
                    customSnippetWrapEl.appendChild(snippetPreviewWrapEl); | 
				
			|||
                    customSnippetWrapEl.appendChild(editCustomSnippetEl); | 
				
			|||
                    containerEl = customSnippetWrapEl; | 
				
			|||
 | 
				
			|||
                    this.__onRenameCustomBtnClick = this._onRenameCustomBtnClick.bind(this); | 
				
			|||
                    renameBtnEl.addEventListener("click", this.__onRenameCustomBtnClick); | 
				
			|||
                    this.__onDeleteCustomBtnClick = this._onDeleteCustomBtnClick.bind(this); | 
				
			|||
                    removeBtnEl.addEventListener("click", this.__onDeleteCustomBtnClick); | 
				
			|||
                } | 
				
			|||
                containerEl.classList.add("invisible"); | 
				
			|||
                leftColEl.appendChild(containerEl); | 
				
			|||
 | 
				
			|||
                // preload images
 | 
				
			|||
                const imageEls = snippetPreviewWrapEl.querySelectorAll("img"); | 
				
			|||
                // TODO: move onceAllImagesLoaded in web_editor and to use it here
 | 
				
			|||
                return Promise.all(Array.from(imageEls).map(imgEl => { | 
				
			|||
                    imgEl.setAttribute("loading", "eager"); | 
				
			|||
                    return new Promise(resolve => { | 
				
			|||
                        if (imgEl.complete) { | 
				
			|||
                            resolve(); | 
				
			|||
                        } else { | 
				
			|||
                            imgEl.onload = () => resolve(); | 
				
			|||
                            imgEl.onerror = () => resolve(); | 
				
			|||
                        } | 
				
			|||
                    }); | 
				
			|||
                })).then(() => containerEl); | 
				
			|||
            })); | 
				
			|||
            if (this.currentInsertSnippetsCallID !== insertSnippetsCallID) { | 
				
			|||
                return; | 
				
			|||
            } | 
				
			|||
            // Balance items into two columns
 | 
				
			|||
            const leftColElements = []; | 
				
			|||
            const rightColElements = []; | 
				
			|||
            for (const itemEl of itemEls) { | 
				
			|||
                const size = itemEl.getBoundingClientRect().height; | 
				
			|||
                if (leftColSize <= rightColSize) { | 
				
			|||
                    leftColElements.push(itemEl); | 
				
			|||
                    leftColSize += size; | 
				
			|||
                } else { | 
				
			|||
                    rightColElements.push(itemEl); | 
				
			|||
                    rightColSize += size; | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
            for (const [colEl, colItemEls] of [ | 
				
			|||
                [leftColEl, leftColElements], | 
				
			|||
                [rightColEl, rightColElements], | 
				
			|||
            ]) { | 
				
			|||
                for (const el of colItemEls) { | 
				
			|||
                    colEl.appendChild(el); | 
				
			|||
                    el.classList.remove("invisible"); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
             // Remove previous content
 | 
				
			|||
            while (rowEl.previousSibling) { | 
				
			|||
                rowEl.previousSibling.remove(); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
        this._updateSnippetContent(this.iframeDocument); | 
				
			|||
    }, | 
				
			|||
    /** | 
				
			|||
     * Show snippet name on snippet hover. | 
				
			|||
     */ | 
				
			|||
    __onSnippetPreviewHover(ev) { | 
				
			|||
    const previewEl = ev.currentTarget; | 
				
			|||
    const title = previewEl.firstElementChild?.getAttribute("title"); | 
				
			|||
    if (!title || previewEl.querySelector(".snippet-hover-title-tooltip")) { | 
				
			|||
        return; | 
				
			|||
    } | 
				
			|||
    const tooltip = document.createElement("div"); | 
				
			|||
    tooltip.className = "snippet-hover-title-tooltip position-absolute top-0 start-50 translate-middle-x bg-dark text-white px-5 py-3 rounded shadow-lg"; | 
				
			|||
    Object.assign(tooltip.style, { | 
				
			|||
        zIndex: "1000", | 
				
			|||
        pointerEvents: "none", | 
				
			|||
        fontSize: "2rem", | 
				
			|||
        fontWeight: "900", | 
				
			|||
        border: "2px solid white", | 
				
			|||
        boxShadow: "0 4px 20px rgba(0, 0, 0, 0.3)", | 
				
			|||
    }); | 
				
			|||
    tooltip.textContent = title; | 
				
			|||
    previewEl.appendChild(tooltip); | 
				
			|||
    const removeTooltip = () => { | 
				
			|||
        tooltip.remove(); | 
				
			|||
        previewEl.removeEventListener("mouseleave", removeTooltip); | 
				
			|||
    }; | 
				
			|||
    previewEl.addEventListener("mouseleave", removeTooltip, { once: true }); | 
				
			|||
}, | 
				
			|||
}); | 
				
			|||