Source code for mindroot.lib.butemplates

import os
from jinja2 import Environment, FileSystemLoader, ChoiceLoader
from .plugins import list_enabled, get_plugin_path
from .parent_templates import get_parent_templates_env


# TODO: get_parent_templates_env(plugins):
# jinja2 environment containing only 1 template per plugin file name,
# the first one that is found from plugins with that name,
# need to look at how jinja2 loaders work

[docs] def setup_template_environment(plugins=None): """Set up Jinja2 environment with multiple template paths. Returns: Environment: Configured Jinja2 environment """ if plugins is None: plugins = list_enabled(False) template_paths = set(['templates']) # Start with default templates directory # Add plugin template paths for plugin in plugins: plugin_path = get_plugin_path(plugin) if plugin_path: # Add main template directory template_dir = os.path.join(plugin_path, 'templates') if os.path.exists(template_dir): template_paths.add(template_dir) print(f"Added template path: {template_dir}") # Add inject directory inject_dir = os.path.join(plugin_path, 'inject') if os.path.exists(inject_dir): template_paths.add(inject_dir) # change color to green background, white text print("\033[92m" + f"Added inject path: {inject_dir}") # reset color print("\033[0m") # Add parent directories to handle absolute paths template_paths.add(os.path.dirname(plugin_path)) template_paths.add(os.path.dirname(os.path.dirname(plugin_path))) # Create environment with multiple loaders loaders = [FileSystemLoader(path) for path in template_paths] env = Environment(loader=ChoiceLoader(loaders)) print(f"Initialized Jinja2 environment with paths: {template_paths}") return env
# Create a Jinja2 environment with multiple template paths env = setup_template_environment() coreplugins =['admin', 'index', 'chat', 'chat_avatar', 'agent', 'jwt_auth', 'home', 'login', 'persona', 'events', 'user_service', 'usage', 'credits', 'startup'] parent_env = get_parent_templates_env(coreplugins)
[docs] async def find_parent_template(page_name, plugins): """Find parent template in enabled plugins. Args: page_name (str): Name of the template page plugins (list): List of enabled plugins Returns: str: Template path if found, None otherwise """ for plugin in plugins: plugin_path = get_plugin_path(plugin) if not plugin_path: print(f'Warning: Could not find path for plugin: {plugin}') continue print(f'Checking plugin: {plugin}, path: {plugin_path}') template_path = os.path.join(plugin_path, 'templates', f'{page_name}.jinja2') if os.path.exists(template_path): print(f'Found parent template in plugin: {template_path}') # Convert to relative path from one of our template roots for loader in env.loader.loaders: for template_dir in loader.searchpath: rel_path = os.path.relpath(template_path, template_dir) if not rel_path.startswith('..'): print(f'Using template path: {rel_path}') return rel_path # Fallback to absolute path if no relative path works return template_path else: print(f'No parent template found in plugin: {plugin}, template path was {template_path}') # Try alternate locations alt_paths = [ os.path.join(plugin_path, 'src', plugin, 'templates', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', 'templates', f'{page_name}.jinja2'), ] for alt_path in alt_paths: if os.path.exists(alt_path): print(f'Found parent template in alternate location: {alt_path}') # Convert to relative path for loader in env.loader.loaders: for template_dir in loader.searchpath: rel_path = os.path.relpath(alt_path, template_dir) if not rel_path.startswith('..'): print(f'Using template path: {rel_path}') return rel_path return alt_path return None
[docs] async def load_plugin_templates(page_name, plugins): """Load templates from plugins. Args: page_name (str): Name of the template page plugins (list): List of enabled plugins Returns: list: List of template info dictionaries """ templates = [] for plugin in plugins: try: plugin_path = get_plugin_path(plugin) if not plugin_path: print(f'Warning: Could not find path for plugin: {plugin}') continue print(f"Loading templates from plugin: {plugin}, path: {plugin_path}") # plugin might be name rather than directory # get the last part of the path to check also # last_part = plugin_path.split('/')[-1] # Check inject templates inject_paths = [ os.path.join(plugin_path, 'inject', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', plugin, 'inject', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', 'inject', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', last_part, 'inject', f'{page_name}.jinja2'), ] for path in inject_paths: if os.path.exists(path): with open(path) as f: print(f"Found inject template at: {path}") templates.append({'type': 'inject', 'template': env.from_string(f.read())}) break else: print(f"Inject template not found at: {path}") # Check override templates override_paths = [ os.path.join(plugin_path, 'override', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', plugin, 'override', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', 'override', f'{page_name}.jinja2'), os.path.join(plugin_path, 'src', last_part, 'override', f'{page_name}.jinja2') ] for path in override_paths: if os.path.exists(path): with open(path) as f: print(f"Found override template at: {path}") templates.append({'type': 'override', 'template': env.from_string(f.read())}) break except Exception as e: print(f'Error loading plugin template: {e}') continue return templates
[docs] async def collect_content(template, blocks, template_type, data): """Collect content from child templates. Args: template: Jinja2 template object blocks (list): List of block names template_type (str): Type of template ('inject' or 'override') data (dict): Template context data Returns: dict: Collected content by block """ content = {block: {'inject': [], 'override': None} for block in blocks} for block in blocks: if block in template.blocks: block_content = ''.join(template.blocks[block](template.new_context(data))) if template_type == 'override': content[block]['override'] = block_content else: content[block]['inject'].append(block_content) return content
[docs] async def render_combined_template(page_name, plugins, context): """Render combined template with injections and overrides. Args: page_name (str): Name of the template page plugins (list): List of enabled plugins context (dict): Template context data Returns: str: Rendered HTML """ print("plugins:", plugins) parent_template_path = await find_parent_template(page_name, plugins) print("\033[92m" + "----------------------------------") print("parent_template_path", parent_template_path) print("page name", page_name, "plugins:", plugins, "context:", context) print("\033[0m") if parent_template_path: try: parent_template = env.get_template(parent_template_path) except Exception as e: print(f"Error loading template {parent_template_path}: {e}") print(f"Template search paths: {[l.searchpath for l in env.loader.loaders]}") raise else: default_path = f'templates/{page_name}.jinja2' if not os.path.exists(default_path): raise FileNotFoundError(f"Template not found: {page_name}") parent_template = env.get_template(default_path) child_templates = await load_plugin_templates(page_name, plugins) parent_blocks = parent_template.blocks.keys() all_content = {block: {'inject': [], 'override': None} for block in parent_blocks} print("child_templates", child_templates) for child_template_info in child_templates: print("calling collect_content") child_content = await collect_content( child_template_info['template'], parent_blocks, child_template_info['type'], context ) for block, content in child_content.items(): if content['override']: all_content[block]['override'] = content['override'] else: all_content[block]['inject'].extend(content['inject']) combined_template_str = '{% extends layout_template %}\n' for block in all_content: if all_content[block]['override']: combined_template_str += f'{{% block {block} %}}\n {{{{ combined_{block}_override|safe }}}}\n{{% endblock %}}\n' else: combined_template_str += f'{{% block {block} %}}\n {{{{ super() }}}}\n {{{{ combined_{block}_inject|safe }}}}\n{{% endblock %}}\n' combined_child_template = env.from_string(combined_template_str) combined_inject = {} combined_override = {} for block, content in all_content.items(): if 'inject' in content and isinstance(content['inject'], list): # print with a yellow background, black text # report details including page name and content injected print("\033[103m" + f"Injecting into block {block} on page {page_name} with content:") print(content['inject']) # reset color print("\033[0m") combined_inject[f'combined_{block}_inject'] = ''.join(content['inject']) else: combined_inject[f'combined_{block}_inject'] = '' if 'override' in content and content['override']: combined_override[f'combined_{block}_override'] = content['override'] print("in render combined, context is", context) rendered_html = combined_child_template.render( layout_template=parent_template, **combined_inject, **combined_override, **context ) return rendered_html
[docs] async def render(page_name, context): """Render a template with plugin injections and overrides. Args: page_name (str): Name of the template page context (dict): Template context data Returns: str: Rendered HTML """ plugins = list_enabled(False) # print with white background red text # report details including page name and plugins enabled print("\033[101m" + f"Rendering page {page_name} with plugins enabled:") print(plugins) # reset color print("\033[0m") return await render_combined_template(page_name, plugins, context)