diff --git a/.pylint_cache/src_1.stats b/.pylint_cache/src_1.stats
new file mode 100644
index 0000000..36012ce
Binary files /dev/null and b/.pylint_cache/src_1.stats differ
diff --git a/src/autometabuilder/messages_en.json b/src/autometabuilder/messages_en.json
index 481beca..61668a4 100644
--- a/src/autometabuilder/messages_en.json
+++ b/src/autometabuilder/messages_en.json
@@ -368,5 +368,22 @@
"ui.workflow.run_when_placeholder": "$flag_key",
"ui.workflow.loop_body_label": "Loop Body",
"ui.workflow.add_loop_node": "Add Node to Loop",
- "ui.workflow.delete_node": "Delete node"
+ "ui.workflow.delete_node": "Delete node",
+ "ui.workflow.templates.title": "Workflow Templates",
+ "ui.workflow.templates.select_label": "Template",
+ "ui.workflow.templates.select_placeholder": "Select a template...",
+ "ui.workflow.templates.description_placeholder": "Choose a template to preview what it does.",
+ "ui.workflow.templates.apply": "Apply Template",
+ "ui.workflow.templates.confirm_apply": "Replace the current workflow with this template?",
+ "ui.workflow.templates.loaded": "Template loaded.",
+ "ui.workflow.templates.error": "Unable to load templates.",
+ "ui.workflow.templates.error_load": "Unable to load the selected template.",
+ "meta.workflow_packages.blank.label": "Blank Canvas",
+ "meta.workflow_packages.blank.description": "Start with an empty workflow.",
+ "meta.workflow_packages.iterative_loop.label": "Iterative Agent Loop",
+ "meta.workflow_packages.iterative_loop.description": "Cycle AI planning, tool execution, and feedback until no tool calls remain.",
+ "meta.workflow_packages.single_pass.label": "Single Pass",
+ "meta.workflow_packages.single_pass.description": "Run one AI request and tool execution sequence without looping.",
+ "meta.workflow_packages.plan_execute_summarize.label": "Plan, Execute, Summarize",
+ "meta.workflow_packages.plan_execute_summarize.description": "Plan with the model, run tools, then ask for a final recap."
}
diff --git a/src/autometabuilder/messages_es.json b/src/autometabuilder/messages_es.json
index 80b7e38..d1904e9 100644
--- a/src/autometabuilder/messages_es.json
+++ b/src/autometabuilder/messages_es.json
@@ -364,5 +364,22 @@
"ui.workflow.run_when_placeholder": "$flag_key",
"ui.workflow.loop_body_label": "Cuerpo del bucle",
"ui.workflow.add_loop_node": "Añadir nodo al bucle",
- "ui.workflow.delete_node": "Eliminar nodo"
+ "ui.workflow.delete_node": "Eliminar nodo",
+ "ui.workflow.templates.title": "Plantillas de flujo de trabajo",
+ "ui.workflow.templates.select_label": "Plantilla",
+ "ui.workflow.templates.select_placeholder": "Selecciona una plantilla...",
+ "ui.workflow.templates.description_placeholder": "Elige una plantilla para ver lo que hace.",
+ "ui.workflow.templates.apply": "Aplicar plantilla",
+ "ui.workflow.templates.confirm_apply": "¿Reemplazar el flujo de trabajo actual con esta plantilla?",
+ "ui.workflow.templates.loaded": "Plantilla cargada.",
+ "ui.workflow.templates.error": "No se pueden cargar las plantillas.",
+ "ui.workflow.templates.error_load": "No se puede cargar la plantilla seleccionada.",
+ "meta.workflow_packages.blank.label": "Lienzo en blanco",
+ "meta.workflow_packages.blank.description": "Empieza con un flujo de trabajo vacío.",
+ "meta.workflow_packages.iterative_loop.label": "Bucle iterativo del agente",
+ "meta.workflow_packages.iterative_loop.description": "Cicla planificación de IA, ejecución de herramientas y retroalimentación hasta que no haya llamadas a herramientas.",
+ "meta.workflow_packages.single_pass.label": "Una sola pasada",
+ "meta.workflow_packages.single_pass.description": "Ejecuta una solicitud de IA y la secuencia de herramientas una vez, sin bucle.",
+ "meta.workflow_packages.plan_execute_summarize.label": "Planear, ejecutar, resumir",
+ "meta.workflow_packages.plan_execute_summarize.description": "Planifica con el modelo, ejecuta herramientas y luego pide un resumen final."
}
diff --git a/src/autometabuilder/messages_fr.json b/src/autometabuilder/messages_fr.json
index a6abec2..9f6c13f 100644
--- a/src/autometabuilder/messages_fr.json
+++ b/src/autometabuilder/messages_fr.json
@@ -364,5 +364,22 @@
"ui.workflow.run_when_placeholder": "$flag_key",
"ui.workflow.loop_body_label": "Corps de la boucle",
"ui.workflow.add_loop_node": "Ajouter un nœud à la boucle",
- "ui.workflow.delete_node": "Supprimer le nœud"
+ "ui.workflow.delete_node": "Supprimer le nœud",
+ "ui.workflow.templates.title": "Modèles de flux de travail",
+ "ui.workflow.templates.select_label": "Modèle",
+ "ui.workflow.templates.select_placeholder": "Sélectionnez un modèle...",
+ "ui.workflow.templates.description_placeholder": "Choisissez un modèle pour voir ce qu'il fait.",
+ "ui.workflow.templates.apply": "Appliquer le modèle",
+ "ui.workflow.templates.confirm_apply": "Remplacer le flux de travail actuel par ce modèle ?",
+ "ui.workflow.templates.loaded": "Modèle chargé.",
+ "ui.workflow.templates.error": "Impossible de charger les modèles.",
+ "ui.workflow.templates.error_load": "Impossible de charger le modèle sélectionné.",
+ "meta.workflow_packages.blank.label": "Toile vierge",
+ "meta.workflow_packages.blank.description": "Commencez avec un workflow vide.",
+ "meta.workflow_packages.iterative_loop.label": "Boucle itérative de l'agent",
+ "meta.workflow_packages.iterative_loop.description": "Itère la planification IA, l'exécution des outils et les retours jusqu'à ce qu'il n'y ait plus d'appels d'outils.",
+ "meta.workflow_packages.single_pass.label": "Passe unique",
+ "meta.workflow_packages.single_pass.description": "Exécute une requête IA et une séquence d'outils une seule fois, sans boucle.",
+ "meta.workflow_packages.plan_execute_summarize.label": "Planifier, exécuter, résumer",
+ "meta.workflow_packages.plan_execute_summarize.description": "Planifiez avec le modèle, exécutez les outils, puis demandez un récapitulatif final."
}
diff --git a/src/autometabuilder/messages_nl.json b/src/autometabuilder/messages_nl.json
index 0dc9b9b..18c23c0 100644
--- a/src/autometabuilder/messages_nl.json
+++ b/src/autometabuilder/messages_nl.json
@@ -364,5 +364,22 @@
"ui.workflow.run_when_placeholder": "$flag_key",
"ui.workflow.loop_body_label": "Lusinhoud",
"ui.workflow.add_loop_node": "Node aan lus toevoegen",
- "ui.workflow.delete_node": "Node verwijderen"
+ "ui.workflow.delete_node": "Node verwijderen",
+ "ui.workflow.templates.title": "Workflow-sjablonen",
+ "ui.workflow.templates.select_label": "Sjabloon",
+ "ui.workflow.templates.select_placeholder": "Selecteer een sjabloon...",
+ "ui.workflow.templates.description_placeholder": "Kies een sjabloon om te zien wat het doet.",
+ "ui.workflow.templates.apply": "Sjabloon toepassen",
+ "ui.workflow.templates.confirm_apply": "Het huidige workflow vervangen door dit sjabloon?",
+ "ui.workflow.templates.loaded": "Sjabloon geladen.",
+ "ui.workflow.templates.error": "Kan sjablonen niet laden.",
+ "ui.workflow.templates.error_load": "Kan het geselecteerde sjabloon niet laden.",
+ "meta.workflow_packages.blank.label": "Leeg canvas",
+ "meta.workflow_packages.blank.description": "Begin met een lege workflow.",
+ "meta.workflow_packages.iterative_loop.label": "Iteratieve agentlus",
+ "meta.workflow_packages.iterative_loop.description": "Herhaalt AI-planning, tooluitvoering en feedback tot er geen tool-aanroepen meer zijn.",
+ "meta.workflow_packages.single_pass.label": "Enkele run",
+ "meta.workflow_packages.single_pass.description": "Voer een AI-aanvraag en toolreeks een keer uit, zonder lus.",
+ "meta.workflow_packages.plan_execute_summarize.label": "Plan, voer uit, vat samen",
+ "meta.workflow_packages.plan_execute_summarize.description": "Plan met het model, voer tools uit en vraag daarna om een eindoverzicht."
}
diff --git a/src/autometabuilder/messages_pirate.json b/src/autometabuilder/messages_pirate.json
index ffe27b8..7e701e6 100644
--- a/src/autometabuilder/messages_pirate.json
+++ b/src/autometabuilder/messages_pirate.json
@@ -364,5 +364,22 @@
"ui.workflow.run_when_placeholder": "$flag_key",
"ui.workflow.loop_body_label": "Loop Body",
"ui.workflow.add_loop_node": "Add Node to Loop",
- "ui.workflow.delete_node": "Delete node"
+ "ui.workflow.delete_node": "Delete node",
+ "ui.workflow.templates.title": "Workflow Treasure Maps",
+ "ui.workflow.templates.select_label": "Template",
+ "ui.workflow.templates.select_placeholder": "Pick a template...",
+ "ui.workflow.templates.description_placeholder": "Pick a template to see what it does.",
+ "ui.workflow.templates.apply": "Hoist Template",
+ "ui.workflow.templates.confirm_apply": "Replace the current workflow with this template?",
+ "ui.workflow.templates.loaded": "Template loaded.",
+ "ui.workflow.templates.error": "Can't load templates.",
+ "ui.workflow.templates.error_load": "Can't load the selected template.",
+ "meta.workflow_packages.blank.label": "Blank Deck",
+ "meta.workflow_packages.blank.description": "Start with an empty workflow.",
+ "meta.workflow_packages.iterative_loop.label": "Iterative Matey Loop",
+ "meta.workflow_packages.iterative_loop.description": "Cycle AI planning, tool work, and feedback till there be no tool calls.",
+ "meta.workflow_packages.single_pass.label": "Single Pass",
+ "meta.workflow_packages.single_pass.description": "Run one AI request and tool run, no loop.",
+ "meta.workflow_packages.plan_execute_summarize.label": "Plan, Plunder, Summarize",
+ "meta.workflow_packages.plan_execute_summarize.description": "Plan with the model, run tools, then ask for a final recap."
}
diff --git a/src/autometabuilder/metadata.json b/src/autometabuilder/metadata.json
index e96e00a..a54a0b3 100644
--- a/src/autometabuilder/metadata.json
+++ b/src/autometabuilder/metadata.json
@@ -1,6 +1,7 @@
{
"tools_path": "tools.json",
"workflow_path": "workflow.json",
+ "workflow_packages_path": "workflow_packages",
"messages": {
"en": "messages_en.json",
"es": "messages_es.json",
diff --git a/src/autometabuilder/web/server.py b/src/autometabuilder/web/server.py
index bc4f8e9..ac4bde3 100644
--- a/src/autometabuilder/web/server.py
+++ b/src/autometabuilder/web/server.py
@@ -216,6 +216,35 @@ def get_workflow_content():
with open(workflow_path, "r", encoding="utf-8") as f:
return f.read()
+def get_workflow_packages_dir():
+ pkg_dir = os.path.dirname(os.path.dirname(__file__))
+ metadata = get_metadata()
+ packages_dir = metadata.get("workflow_packages_path", "workflow_packages")
+ return os.path.join(pkg_dir, packages_dir)
+
+def load_workflow_packages():
+ packages_dir = get_workflow_packages_dir()
+ if not os.path.isdir(packages_dir):
+ return []
+ packages = []
+ for filename in sorted(os.listdir(packages_dir)):
+ if not filename.endswith(".json"):
+ continue
+ path = os.path.join(packages_dir, filename)
+ try:
+ with open(path, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ except json.JSONDecodeError:
+ continue
+ if not isinstance(data, dict):
+ continue
+ package_id = data.get("id") or os.path.splitext(filename)[0]
+ data["id"] = package_id
+ if "workflow" not in data:
+ data["workflow"] = {"nodes": []}
+ packages.append(data)
+ return packages
+
@app.get("/", response_class=HTMLResponse)
async def read_item(request: Request, username: str = Depends(get_current_user)):
logs = get_recent_logs()
@@ -318,6 +347,27 @@ async def get_workflow_plugins(username: str = Depends(get_current_user)):
metadata = get_metadata()
return metadata.get("workflow_plugins", {})
+@app.get("/api/workflow/packages", response_class=JSONResponse)
+async def list_workflow_packages(username: str = Depends(get_current_user)):
+ packages = load_workflow_packages()
+ summarized = []
+ for package in packages:
+ summarized.append({
+ "id": package.get("id"),
+ "label": package.get("label", ""),
+ "description": package.get("description", ""),
+ "tags": package.get("tags", [])
+ })
+ return {"packages": summarized}
+
+@app.get("/api/workflow/packages/{package_id}", response_class=JSONResponse)
+async def get_workflow_package(package_id: str, username: str = Depends(get_current_user)):
+ packages = load_workflow_packages()
+ for package in packages:
+ if package.get("id") == package_id:
+ return package
+ raise HTTPException(status_code=404, detail="Workflow package not found")
+
@app.get("/api/logs")
async def get_logs(username: str = Depends(get_current_user)):
return {"logs": get_recent_logs()}
diff --git a/src/autometabuilder/web/static/js/index.js b/src/autometabuilder/web/static/js/index.js
index 302903c..4241148 100644
--- a/src/autometabuilder/web/static/js/index.js
+++ b/src/autometabuilder/web/static/js/index.js
@@ -7,13 +7,120 @@
const format = (text, values = {}) => text.replace(/\{(\w+)\}/g, (_, name) => values[name] ?? '');
const fetchWorkflowPlugins = async () => {
- const response = await fetch('/api/workflow/plugins');
+ const response = await fetch('/api/workflow/plugins', { credentials: 'include' });
if (!response.ok) {
throw new Error(`Plugin fetch failed: ${response.status}`);
}
return response.json();
};
+ const fetchWorkflowPackages = async () => {
+ const response = await fetch('/api/workflow/packages', { credentials: 'include' });
+ if (!response.ok) {
+ throw new Error(`Package fetch failed: ${response.status}`);
+ }
+ return response.json();
+ };
+
+ const WorkflowTemplates = {
+ packages: [],
+ selectEl: null,
+ descEl: null,
+ applyBtn: null,
+
+ async init() {
+ this.selectEl = document.getElementById('workflow-template-select');
+ this.descEl = document.getElementById('workflow-template-description');
+ this.applyBtn = document.getElementById('workflow-template-apply');
+ if (!this.selectEl) return;
+
+ try {
+ const data = await fetchWorkflowPackages();
+ this.packages = data.packages || [];
+ this.renderOptions();
+ this.updateDescription();
+ } catch (error) {
+ console.error('Workflow template fetch failed', error);
+ if (this.descEl) {
+ this.descEl.textContent = t('ui.workflow.templates.error', 'Unable to load templates.');
+ }
+ return;
+ }
+
+ this.selectEl.addEventListener('change', () => this.updateDescription());
+ this.applyBtn?.addEventListener('click', () => this.applySelected());
+ },
+
+ renderOptions() {
+ if (!this.selectEl) return;
+ this.selectEl.innerHTML = '';
+ const placeholder = document.createElement('option');
+ placeholder.value = '';
+ placeholder.textContent = t('ui.workflow.templates.select_placeholder', 'Select a template...');
+ placeholder.disabled = true;
+ placeholder.selected = true;
+ this.selectEl.appendChild(placeholder);
+
+ this.packages.forEach(pkg => {
+ const option = document.createElement('option');
+ option.value = pkg.id;
+ option.textContent = t(pkg.label || pkg.id, pkg.label || pkg.id);
+ this.selectEl.appendChild(option);
+ });
+ },
+
+ updateDescription() {
+ if (!this.descEl) return;
+ const selected = this.getSelectedPackage();
+ if (this.applyBtn) {
+ this.applyBtn.disabled = !selected;
+ }
+ if (!selected) {
+ this.descEl.textContent = t(
+ 'ui.workflow.templates.description_placeholder',
+ 'Choose a template to preview what it does.'
+ );
+ return;
+ }
+ const description = selected.description || '';
+ this.descEl.textContent = t(description, description);
+ },
+
+ getSelectedPackage() {
+ if (!this.selectEl) return null;
+ const selectedId = this.selectEl.value;
+ return this.packages.find(pkg => pkg.id === selectedId) || null;
+ },
+
+ async applySelected() {
+ const selected = this.getSelectedPackage();
+ if (!selected) return;
+ const confirmText = t(
+ 'ui.workflow.templates.confirm_apply',
+ 'Replace the current workflow with this template?'
+ );
+ if (!confirm(confirmText)) return;
+
+ try {
+ const response = await fetch(`/api/workflow/packages/${selected.id}`, { credentials: 'include' });
+ if (!response.ok) {
+ throw new Error(`Template fetch failed: ${response.status}`);
+ }
+ const data = await response.json();
+ const workflow = data.workflow || data;
+ if (window.WorkflowBuilder && typeof window.WorkflowBuilder.loadWorkflow === 'function') {
+ window.WorkflowBuilder.loadWorkflow(workflow);
+ }
+ if (window.Toast) {
+ Toast.show(t('ui.workflow.templates.loaded', 'Template loaded.'), 'success');
+ }
+ } catch (error) {
+ console.error('Workflow template load failed', error);
+ alert(t('ui.workflow.templates.error_load', 'Unable to load the selected template.'));
+ }
+ }
+ };
+
const initWorkflowBuilder = (pluginDefinitions) => {
if (!window.WorkflowBuilder) return;
const container = document.getElementById('workflow-builder');
@@ -367,6 +474,7 @@ model: ${model}
} catch (error) {
console.error('Workflow builder failed to initialize', error);
}
+ await WorkflowTemplates.init();
wireRunModeToggles();
wirePromptChips();
};
diff --git a/src/autometabuilder/web/static/js/workflow.js b/src/autometabuilder/web/static/js/workflow.js
index 9e3d968..e78eccb 100644
--- a/src/autometabuilder/web/static/js/workflow.js
+++ b/src/autometabuilder/web/static/js/workflow.js
@@ -362,6 +362,15 @@ const WorkflowBuilder = {
}
},
+ loadWorkflow(workflow) {
+ if (!workflow || !Array.isArray(workflow.nodes)) {
+ this.workflow = { nodes: [] };
+ } else {
+ this.workflow = workflow;
+ }
+ this.render();
+ },
+
escapeHtml(text) {
if (text === null || text === undefined) return '';
const div = document.createElement('div');
diff --git a/src/autometabuilder/web/templates/base.html b/src/autometabuilder/web/templates/base.html
index 712d4c0..c03b839 100644
--- a/src/autometabuilder/web/templates/base.html
+++ b/src/autometabuilder/web/templates/base.html
@@ -20,40 +20,15 @@
{% block head %}{% endblock %}
-
-
+ {% from "components/organisms/sidebar.html" import sidebar %}
+ {% set nav_items = [
+ {'section': 'dashboard', 'icon': 'speedometer2', 'label': t('ui.nav.dashboard', 'Dashboard'), 'active': True},
+ {'section': 'workflow', 'icon': 'diagram-3', 'label': t('ui.nav.workflow', 'Workflow'), 'active': False},
+ {'section': 'prompt', 'icon': 'file-text', 'label': t('ui.nav.prompt', 'Prompt'), 'active': False},
+ {'section': 'settings', 'icon': 'gear', 'label': t('ui.nav.settings', 'Settings'), 'active': False},
+ {'section': 'translations', 'icon': 'translate', 'label': t('ui.nav.translations', 'Translations'), 'active': False}
+ ] %}
+ {{ sidebar(nav_items, t('ui.app.name', 'AutoMetabuilder'), username, t('ui.theme_toggle', 'Toggle theme')) }}
diff --git a/src/autometabuilder/web/templates/components/atoms/nav_link.html b/src/autometabuilder/web/templates/components/atoms/nav_link.html
new file mode 100644
index 0000000..6543546
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/atoms/nav_link.html
@@ -0,0 +1,6 @@
+{# Sidebar navigation link macro. #}
+{% macro nav_link(section, icon_name, label, is_active=False) -%}
+
+ {{ label }}
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/molecules/sidebar_nav.html b/src/autometabuilder/web/templates/components/molecules/sidebar_nav.html
new file mode 100644
index 0000000..5a3d626
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/molecules/sidebar_nav.html
@@ -0,0 +1,10 @@
+{# Sidebar navigation list macro. #}
+{% from "components/atoms/nav_link.html" import nav_link %}
+
+{% macro sidebar_nav(items) -%}
+
+ {% for item in items %}
+ {{ nav_link(item.section, item.icon, item.label, item.active) }}
+ {% endfor %}
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/prompt_builder.html b/src/autometabuilder/web/templates/components/organisms/prompt_builder.html
new file mode 100644
index 0000000..a74680e
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/prompt_builder.html
@@ -0,0 +1,71 @@
+{# Prompt builder card macro. #}
+{% from "components/atoms/helper_text.html" import helper_text %}
+{% from "components/molecules/card.html" import card %}
+{% from "components/sections/prompt_step.html" import prompt_step %}
+
+{% macro prompt_builder() -%}
+{% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %}
+
+{% endcall %}
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/prompt_guidance.html b/src/autometabuilder/web/templates/components/organisms/prompt_guidance.html
new file mode 100644
index 0000000..c9cbedd
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/prompt_guidance.html
@@ -0,0 +1,21 @@
+{# Prompt guidance card macro. #}
+{% from "components/molecules/card.html" import card %}
+
+{% macro prompt_guidance() -%}
+{% call card(t('ui.prompt.guidance.title', 'Guidance'), "lightbulb") %}
+
+
+
{{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }}
+
{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}
+
+
+
{{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }}
+
{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}
+
+
+
{{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }}
+
{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}
+
+
+{% endcall %}
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/settings_api_keys.html b/src/autometabuilder/web/templates/components/organisms/settings_api_keys.html
new file mode 100644
index 0000000..1d30df9
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/settings_api_keys.html
@@ -0,0 +1,31 @@
+{# Settings API keys card macro. #}
+{% macro settings_api_keys() -%}
+{% set settings_desc = metadata.get('settings_descriptions', {}) %}
+
+
+
+ {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+ {% set desc_label = desc.get('label', key) %}
+ {% set desc_text = desc.get('description', '') %}
+ {% set desc_placeholder = desc.get('placeholder', '') %}
+
+ {% endfor %}
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/settings_configuration.html b/src/autometabuilder/web/templates/components/organisms/settings_configuration.html
new file mode 100644
index 0000000..e0df7f1
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/settings_configuration.html
@@ -0,0 +1,37 @@
+{# Settings configuration card macro. #}
+{% macro settings_configuration() -%}
+{% set settings_desc = metadata.get('settings_descriptions', {}) %}
+
+
+
+ {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+ {% set desc_label = desc.get('label', key) %}
+ {% set desc_text = desc.get('description', '') %}
+ {% set desc_placeholder = desc.get('placeholder', '') %}
+
+ {% endfor %}
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/settings_other.html b/src/autometabuilder/web/templates/components/organisms/settings_other.html
new file mode 100644
index 0000000..75dfa09
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/settings_other.html
@@ -0,0 +1,44 @@
+{# Settings other settings card macro. #}
+{% macro settings_other() -%}
+{% set settings_desc = metadata.get('settings_descriptions', {}) %}
+{% set known_keys = ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY', 'GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH', 'WEB_USER', 'WEB_PASSWORD'] %}
+
+
+
+
+ {% for key, value in env_vars.items() %}
+ {% if key not in known_keys %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/settings_web_access.html b/src/autometabuilder/web/templates/components/organisms/settings_web_access.html
new file mode 100644
index 0000000..1027bd7
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/settings_web_access.html
@@ -0,0 +1,26 @@
+{# Settings web access card macro. #}
+{% macro settings_web_access() -%}
+{% set settings_desc = metadata.get('settings_descriptions', {}) %}
+
+
+
+ {% for key in ['WEB_USER', 'WEB_PASSWORD'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+ {% set desc_label = desc.get('label', key) %}
+ {% set desc_text = desc.get('description', '') %}
+
+ {% endfor %}
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/sidebar.html b/src/autometabuilder/web/templates/components/organisms/sidebar.html
new file mode 100644
index 0000000..d8c0ef5
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/sidebar.html
@@ -0,0 +1,12 @@
+{# Sidebar macro. #}
+{% from "components/organisms/sidebar_header.html" import sidebar_header %}
+{% from "components/molecules/sidebar_nav.html" import sidebar_nav %}
+{% from "components/organisms/sidebar_footer.html" import sidebar_footer %}
+
+{% macro sidebar(nav_items, app_name, username, toggle_title) -%}
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/sidebar_footer.html b/src/autometabuilder/web/templates/components/organisms/sidebar_footer.html
new file mode 100644
index 0000000..891cd89
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/sidebar_footer.html
@@ -0,0 +1,12 @@
+{# Sidebar footer macro. #}
+{% macro sidebar_footer(username, toggle_title) -%}
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/sidebar_header.html b/src/autometabuilder/web/templates/components/organisms/sidebar_header.html
new file mode 100644
index 0000000..5ee546d
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/sidebar_header.html
@@ -0,0 +1,6 @@
+{# Sidebar header macro. #}
+{% macro sidebar_header(app_name) -%}
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/translations_editor.html b/src/autometabuilder/web/templates/components/organisms/translations_editor.html
new file mode 100644
index 0000000..c497245
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/translations_editor.html
@@ -0,0 +1,62 @@
+{# Translations editor card macro. #}
+{% from "components/molecules/empty_state.html" import empty_state %}
+
+{% macro translations_editor() -%}
+
+
+
+
+ {{ empty_state("translate", t('ui.translations.empty.title', 'Pick a language'), t('ui.translations.empty.body', 'Select a language from the list to start editing translations.')) }}
+
+
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/translations_languages.html b/src/autometabuilder/web/templates/components/organisms/translations_languages.html
new file mode 100644
index 0000000..e3d7017
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/translations_languages.html
@@ -0,0 +1,48 @@
+{# Translations languages card macro. #}
+{% macro translations_languages() -%}
+
+
+
+
+ {% for lang, file in translations.items() %}
+
+
+
+
+ {{ lang.upper() }}
+ {{ file }}
+
+
+
+
+
+
+ {% if lang != 'en' %}
+
+
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/organisms/workflow_templates.html b/src/autometabuilder/web/templates/components/organisms/workflow_templates.html
new file mode 100644
index 0000000..b7d643f
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/organisms/workflow_templates.html
@@ -0,0 +1,30 @@
+{# Workflow templates card macro. #}
+{% macro workflow_templates() -%}
+
+
+
+
+
+
+ {{ t('ui.workflow.templates.select_label', 'Template') }}
+
+
+ {{ t('ui.workflow.templates.select_placeholder', 'Select a template...') }}
+
+
+
+
+ {{ t('ui.workflow.templates.description_placeholder', 'Choose a template to preview what it does.') }}
+
+
+
+
+ {{ t('ui.workflow.templates.apply', 'Apply Template') }}
+
+
+
+
+
+{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/sections/prompt_section.html b/src/autometabuilder/web/templates/components/sections/prompt_section.html
index 957c519..db037a1 100644
--- a/src/autometabuilder/web/templates/components/sections/prompt_section.html
+++ b/src/autometabuilder/web/templates/components/sections/prompt_section.html
@@ -1,8 +1,7 @@
{# Prompt section macro. #}
{% from "components/atoms/section_header.html" import section_header %}
-{% from "components/atoms/helper_text.html" import helper_text %}
-{% from "components/molecules/card.html" import card %}
-{% from "components/sections/prompt_step.html" import prompt_step %}
+{% from "components/organisms/prompt_builder.html" import prompt_builder with context %}
+{% from "components/organisms/prompt_guidance.html" import prompt_guidance with context %}
{% macro prompt_section() -%}
@@ -10,89 +9,11 @@
- {% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %}
-
-
-
- {% set system_chips = [
- {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.senior.snippet', 'You are a senior software engineer focused on correctness and clarity.'), 'label': t('ui.prompt.chip.senior.label', 'Senior engineer')},
- {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.ask.snippet', 'Ask clarifying questions before making risky changes.'), 'label': t('ui.prompt.chip.ask.label', 'Ask questions')},
- {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.minimal.snippet', 'Prefer minimal diffs and explain trade-offs.'), 'label': t('ui.prompt.chip.minimal.label', 'Minimal diffs')}
- ] %}
- {{ prompt_step(1,
- t('ui.prompt.step1.title', 'Define the assistant'),
- t('ui.prompt.step1.desc', 'Describe who the assistant is and how it should behave.'),
- 'system-prompt',
- 'system_content',
- t('ui.prompt.step1.placeholder', 'You are a senior software engineer who prefers small, safe changes.'),
- (prompt_content | extract_system_content),
- 6,
- system_chips
- ) }}
-
- {% set user_chips = [
- {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.ux.snippet', 'Focus on UX polish, and avoid major refactors.'), 'label': t('ui.prompt.chip.ux.label', 'UX polish')},
- {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.tests.snippet', 'Add tests when possible, but avoid heavy scaffolding.'), 'label': t('ui.prompt.chip.tests.label', 'Add tests')},
- {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.summarize.snippet', 'Summarize changes and suggest next steps.'), 'label': t('ui.prompt.chip.summarize.label', 'Summarize')}
- ] %}
- {{ prompt_step(2,
- t('ui.prompt.step2.title', 'Give the mission'),
- t('ui.prompt.step2.desc', 'Explain what the bot should accomplish right now.'),
- 'user-prompt',
- 'user_content',
- t('ui.prompt.step2.placeholder', 'Review the repo, improve the UI, and summarize what changed.'),
- (prompt_content | extract_user_content),
- 5,
- user_chips
- ) }}
-
-
- {{ t('ui.prompt.model.label', 'Choose a model') }}
- {{ helper_text(t('ui.prompt.model.desc', 'Pick the balance of quality and speed that fits the task.')) }}
-
- GPT-4o ({{ t('ui.prompt.model.recommended', 'Recommended') }})
- GPT-4o Mini ({{ t('ui.prompt.model.faster', 'Faster') }})
- GPT-4 Turbo
- Claude 3.5 Sonnet
-
-
-
-
-
- {{ t('ui.prompt.raw.label', 'Advanced YAML') }}
- {{ helper_text(t('ui.prompt.raw.desc', 'Edit the full YAML only if you need fine control.')) }}
- {{ prompt_content }}
-
-
-
-
- {{ t('ui.prompt.save', 'Save Prompt') }}
-
-
- {{ t('ui.prompt.advanced_yaml', 'Advanced YAML') }}
-
-
-
- {% endcall %}
+ {{ prompt_builder() }}
- {% call card(t('ui.prompt.guidance.title', 'Guidance'), "lightbulb") %}
-
-
-
{{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }}
-
{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}
-
-
-
{{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }}
-
{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}
-
-
-
{{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }}
-
{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}
-
-
- {% endcall %}
+ {{ prompt_guidance() }}
diff --git a/src/autometabuilder/web/templates/components/sections/settings_section.html b/src/autometabuilder/web/templates/components/sections/settings_section.html
index d1de71d..99d63c4 100644
--- a/src/autometabuilder/web/templates/components/sections/settings_section.html
+++ b/src/autometabuilder/web/templates/components/sections/settings_section.html
@@ -1,6 +1,10 @@
{# Settings section macro. #}
{% from "components/atoms/section_header.html" import section_header %}
{% from "components/organisms/callout.html" import callout %}
+{% from "components/organisms/settings_api_keys.html" import settings_api_keys with context %}
+{% from "components/organisms/settings_configuration.html" import settings_configuration with context %}
+{% from "components/organisms/settings_web_access.html" import settings_web_access with context %}
+{% from "components/organisms/settings_other.html" import settings_other with context %}
{% macro settings_section() -%}
@@ -10,138 +14,17 @@
- {% set settings_desc = metadata.get('settings_descriptions', {}) %}
-
-
-
-
- {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %}
- {% set desc = settings_desc.get(key, {}) %}
- {% set desc_label = desc.get('label', key) %}
- {% set desc_text = desc.get('description', '') %}
- {% set desc_placeholder = desc.get('placeholder', '') %}
-
- {% endfor %}
-
-
+ {{ settings_api_keys() }}
-
-
-
- {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %}
- {% set desc = settings_desc.get(key, {}) %}
- {% set desc_label = desc.get('label', key) %}
- {% set desc_text = desc.get('description', '') %}
- {% set desc_placeholder = desc.get('placeholder', '') %}
-
- {% endfor %}
-
-
-
-
-
-
- {% for key in ['WEB_USER', 'WEB_PASSWORD'] %}
- {% set desc = settings_desc.get(key, {}) %}
- {% set desc_label = desc.get('label', key) %}
- {% set desc_text = desc.get('description', '') %}
-
- {% endfor %}
-
-
+ {{ settings_configuration() }}
+ {{ settings_web_access() }}
-
-
-
-
- {% set known_keys = ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY', 'GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH', 'WEB_USER', 'WEB_PASSWORD'] %}
- {% for key, value in env_vars.items() %}
- {% if key not in known_keys %}
-
- {% endif %}
- {% endfor %}
-
-
-
-
-
+ {{ settings_other() }}
{%- endmacro %}
diff --git a/src/autometabuilder/web/templates/components/sections/translations_section.html b/src/autometabuilder/web/templates/components/sections/translations_section.html
index 3d1d35a..e33fd7c 100644
--- a/src/autometabuilder/web/templates/components/sections/translations_section.html
+++ b/src/autometabuilder/web/templates/components/sections/translations_section.html
@@ -1,6 +1,7 @@
{# Translations section macro. #}
{% from "components/atoms/section_header.html" import section_header %}
-{% from "components/molecules/empty_state.html" import empty_state %}
+{% from "components/organisms/translations_languages.html" import translations_languages with context %}
+{% from "components/organisms/translations_editor.html" import translations_editor with context %}
{% macro translations_section() -%}
@@ -8,111 +9,11 @@
-
-
-
-
- {% for lang, file in translations.items() %}
-
-
-
-
- {{ lang.upper() }}
- {{ file }}
-
-
-
-
-
-
- {% if lang != 'en' %}
-
-
-
- {% endif %}
-
-
- {% endfor %}
-
-
-
-
+ {{ translations_languages() }}
-
-
-
-
- {{ empty_state("translate", t('ui.translations.empty.title', 'Pick a language'), t('ui.translations.empty.body', 'Select a language from the list to start editing translations.')) }}
-
-
-
-
+ {{ translations_editor() }}
diff --git a/src/autometabuilder/web/templates/components/sections/workflow_section.html b/src/autometabuilder/web/templates/components/sections/workflow_section.html
index 1decc91..b47014c 100644
--- a/src/autometabuilder/web/templates/components/sections/workflow_section.html
+++ b/src/autometabuilder/web/templates/components/sections/workflow_section.html
@@ -1,10 +1,13 @@
{# Workflow section macro. #}
{% from "components/atoms/section_header.html" import section_header %}
+{% from "components/organisms/workflow_templates.html" import workflow_templates with context %}
{% macro workflow_section() -%}
{{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }}
+ {{ workflow_templates() }}
+