mirror of
https://github.com/johndoe6345789/AutoMetabuilder.git
synced 2026-04-24 13:54:59 +00:00
Modularize UI with component macros and expand workflow features: Introduce Jinja2 macros for components to enhance maintainability and code reuse. Add new templates and i18n message keys for comprehensive workflow package management. Update workflow.js to include a new function for loading workflows.
This commit is contained in:
BIN
.pylint_cache/src_1.stats
Normal file
BIN
.pylint_cache/src_1.stats
Normal file
Binary file not shown.
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -20,40 +20,15 @@
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="amb-layout">
|
||||
<!-- Sidebar -->
|
||||
<aside class="amb-sidebar">
|
||||
<div class="amb-sidebar-header">
|
||||
<h5><i class="bi bi-cpu"></i> {{ t('ui.app.name', 'AutoMetabuilder') }}</h5>
|
||||
</div>
|
||||
|
||||
<nav class="amb-nav">
|
||||
<a href="#dashboard" class="amb-nav-link active" data-section="dashboard">
|
||||
<i class="bi bi-speedometer2"></i> {{ t('ui.nav.dashboard', 'Dashboard') }}
|
||||
</a>
|
||||
<a href="#workflow" class="amb-nav-link" data-section="workflow">
|
||||
<i class="bi bi-diagram-3"></i> {{ t('ui.nav.workflow', 'Workflow') }}
|
||||
</a>
|
||||
<a href="#prompt" class="amb-nav-link" data-section="prompt">
|
||||
<i class="bi bi-file-text"></i> {{ t('ui.nav.prompt', 'Prompt') }}
|
||||
</a>
|
||||
<a href="#settings" class="amb-nav-link" data-section="settings">
|
||||
<i class="bi bi-gear"></i> {{ t('ui.nav.settings', 'Settings') }}
|
||||
</a>
|
||||
<a href="#translations" class="amb-nav-link" data-section="translations">
|
||||
<i class="bi bi-translate"></i> {{ t('ui.nav.translations', 'Translations') }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="amb-sidebar-footer">
|
||||
<div class="amb-user">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
<button class="amb-theme-toggle" data-theme-toggle title="{{ t('ui.theme_toggle', 'Toggle theme') }}">
|
||||
<i class="bi bi-sun-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
{% 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')) }}
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="amb-main">
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{# Sidebar navigation link macro. #}
|
||||
{% macro nav_link(section, icon_name, label, is_active=False) -%}
|
||||
<a href="#{{ section }}" class="amb-nav-link{% if is_active %} active{% endif %}" data-section="{{ section }}">
|
||||
<i class="bi bi-{{ icon_name }}"></i> {{ label }}
|
||||
</a>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{# Sidebar navigation list macro. #}
|
||||
{% from "components/atoms/nav_link.html" import nav_link %}
|
||||
|
||||
{% macro sidebar_nav(items) -%}
|
||||
<nav class="amb-nav">
|
||||
{% for item in items %}
|
||||
{{ nav_link(item.section, item.icon, item.label, item.active) }}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{%- endmacro %}
|
||||
@@ -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") %}
|
||||
<form action="/prompt" method="post" id="prompt-form">
|
||||
<input type="hidden" name="prompt_mode" id="prompt-mode" value="builder">
|
||||
<div id="prompt-builder">
|
||||
{% 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
|
||||
) }}
|
||||
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t('ui.prompt.model.label', 'Choose a model') }}</label>
|
||||
{{ helper_text(t('ui.prompt.model.desc', 'Pick the balance of quality and speed that fits the task.')) }}
|
||||
<select name="model" class="form-select" data-choices>
|
||||
<option value="openai/gpt-4o" {% if 'gpt-4o' in prompt_content %}selected{% endif %}>GPT-4o ({{ t('ui.prompt.model.recommended', 'Recommended') }})</option>
|
||||
<option value="openai/gpt-4o-mini" {% if 'gpt-4o-mini' in prompt_content %}selected{% endif %}>GPT-4o Mini ({{ t('ui.prompt.model.faster', 'Faster') }})</option>
|
||||
<option value="openai/gpt-4-turbo" {% if 'gpt-4-turbo' in prompt_content %}selected{% endif %}>GPT-4 Turbo</option>
|
||||
<option value="anthropic/claude-3-5-sonnet" {% if 'claude' in prompt_content %}selected{% endif %}>Claude 3.5 Sonnet</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="prompt-raw" class="d-none">
|
||||
<label class="amb-form-label">{{ t('ui.prompt.raw.label', 'Advanced YAML') }}</label>
|
||||
{{ helper_text(t('ui.prompt.raw.desc', 'Edit the full YAML only if you need fine control.')) }}
|
||||
<textarea name="content" class="amb-code-editor" id="prompt-yaml" rows="18">{{ prompt_content }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="submit" class="btn btn-success" onclick="buildPromptYaml()">
|
||||
<i class="bi bi-save"></i> {{ t('ui.prompt.save', 'Save Prompt') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="toggleRawPrompt()">
|
||||
<i class="bi bi-code-slash"></i> {{ t('ui.prompt.advanced_yaml', 'Advanced YAML') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{%- endmacro %}
|
||||
@@ -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") %}
|
||||
<div class="amb-guidance">
|
||||
<div class="mb-3">
|
||||
<strong>{{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>{{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{# Settings API keys card macro. #}
|
||||
{% macro settings_api_keys() -%}
|
||||
{% set settings_desc = metadata.get('settings_descriptions', {}) %}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-key"></i> {{ t('ui.settings.api_keys', 'API Keys') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">
|
||||
{{ t(desc_label, desc_label) }}
|
||||
{% if desc.get('required') %}<span class="amb-required">*</span>{% endif %}
|
||||
</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, '') }}"
|
||||
placeholder="{{ t(desc_placeholder, desc_placeholder) }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{# Settings configuration card macro. #}
|
||||
{% macro settings_configuration() -%}
|
||||
{% set settings_desc = metadata.get('settings_descriptions', {}) %}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-gear"></i> {{ t('ui.settings.configuration', 'Configuration') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
{% if desc.get('type') == 'select' %}
|
||||
<select name="env_{{ key }}" class="form-select" data-choices>
|
||||
<option value="">{{ t('ui.common.select_placeholder', 'Select...') }}</option>
|
||||
{% for opt in desc.get('options', []) %}
|
||||
<option value="{{ opt }}" {% if env_vars.get(key) == opt %}selected{% endif %}>{{ opt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, desc.get('default', '')) }}"
|
||||
placeholder="{{ t(desc_placeholder, desc_placeholder) }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -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'] %}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-sliders"></i> {{ t('ui.settings.other', 'Other Settings') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
<div class="row">
|
||||
{% for key, value in env_vars.items() %}
|
||||
{% if key not in known_keys %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="amb-form-group">
|
||||
{% set desc = settings_desc.get(key, {}) %}
|
||||
{% set desc_label = desc.get('label', key) %}
|
||||
{% set desc_text = desc.get('description', t('ui.settings.custom_default_desc', 'Custom environment setting. Add a description in metadata.json to show it here.')) %}
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
<input type="text" name="env_{{ key }}" class="form-control" value="{{ value }}"
|
||||
placeholder="{{ t(desc.get('placeholder', ''), desc.get('placeholder', '')) }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t('ui.settings.add.title', 'Add New Setting') }}</label>
|
||||
<p class="text-muted small mb-2">{{ t('ui.settings.add.desc', 'Use uppercase keys with underscores, like API_TIMEOUT.') }}</p>
|
||||
<div class="input-group">
|
||||
<input type="text" name="new_env_key" class="form-control" placeholder="{{ t('ui.settings.add.placeholder_key', 'KEY') }}">
|
||||
<input type="text" name="new_env_value" class="form-control" placeholder="{{ t('ui.settings.add.placeholder_value', 'Value') }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-footer">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-save"></i> {{ t('ui.settings.save_all', 'Save All Settings') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,26 @@
|
||||
{# Settings web access card macro. #}
|
||||
{% macro settings_web_access() -%}
|
||||
{% set settings_desc = metadata.get('settings_descriptions', {}) %}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-shield-lock"></i> {{ t('ui.settings.web_access', 'Web UI Access') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, '') }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -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) -%}
|
||||
<aside class="amb-sidebar">
|
||||
{{ sidebar_header(app_name) }}
|
||||
{{ sidebar_nav(nav_items) }}
|
||||
{{ sidebar_footer(username, toggle_title) }}
|
||||
</aside>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{# Sidebar footer macro. #}
|
||||
{% macro sidebar_footer(username, toggle_title) -%}
|
||||
<div class="amb-sidebar-footer">
|
||||
<div class="amb-user">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
<button class="amb-theme-toggle" data-theme-toggle title="{{ toggle_title }}">
|
||||
<i class="bi bi-sun-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{# Sidebar header macro. #}
|
||||
{% macro sidebar_header(app_name) -%}
|
||||
<div class="amb-sidebar-header">
|
||||
<h5><i class="bi bi-cpu"></i> {{ app_name }}</h5>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,62 @@
|
||||
{# Translations editor card macro. #}
|
||||
{% from "components/molecules/empty_state.html" import empty_state %}
|
||||
|
||||
{% macro translations_editor() -%}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-pencil-square"></i> <span id="editor-title">{{ t('ui.translations.editor.title', 'Translation Editor') }}</span></h5>
|
||||
<div id="editor-actions" class="amb-translation-actions" style="display: none;">
|
||||
<span id="missing-count" class="amb-pill amb-pill-warning">{{ t('ui.translations.missing_count', '{count} missing') | replace('{count}', '0') }}</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.fillMissing()">
|
||||
<i class="bi bi-magic"></i> {{ t('ui.translations.actions.fill_missing', 'Fill Missing') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.reset()">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> {{ t('ui.actions.reset', 'Reset') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="TranslationEditor.save()">
|
||||
<i class="bi bi-save"></i> {{ t('ui.actions.save', 'Save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
<div id="translation-editor-placeholder">
|
||||
{{ 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.')) }}
|
||||
</div>
|
||||
<div id="translation-editor" style="display: none;">
|
||||
<div class="amb-translation-toolbar">
|
||||
<div class="amb-translation-search">
|
||||
<input type="search" class="form-control form-control-sm" id="translation-search"
|
||||
placeholder="{{ t('ui.translations.search.placeholder', 'Search keys or text...') }}" oninput="TranslationEditor.filter(this.value)">
|
||||
</div>
|
||||
<div class="form-check form-switch amb-translation-toggle">
|
||||
<input class="form-check-input" type="checkbox" id="translation-missing-toggle" checked
|
||||
onchange="TranslationEditor.toggleMissing(this.checked)">
|
||||
<label class="form-check-label small" for="translation-missing-toggle">{{ t('ui.translations.toggle_missing', 'Show missing keys') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="amb-translation-add">
|
||||
<input type="text" class="form-control form-control-sm" id="new-translation-key" placeholder="{{ t('ui.translations.add.key_placeholder', 'new.key') }}">
|
||||
<input type="text" class="form-control form-control-sm" id="new-translation-value" placeholder="{{ t('ui.translations.add.value_placeholder', 'Translation text') }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.prefillNewValue()">
|
||||
{{ t('ui.translations.add.use_english', 'Use English') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="TranslationEditor.addEntry()">
|
||||
<i class="bi bi-plus-lg"></i> {{ t('ui.translations.add.add_key', 'Add Key') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm" id="translation-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%;">{{ t('ui.translations.table.key', 'Key') }}</th>
|
||||
<th>{{ t('ui.translations.table.translation', 'Translation') }}</th>
|
||||
<th style="width: 70px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,48 @@
|
||||
{# Translations languages card macro. #}
|
||||
{% macro translations_languages() -%}
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-globe"></i> {{ t('ui.translations.languages', 'Languages') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body p-0">
|
||||
<div class="list-group list-group-flush" id="translation-list">
|
||||
{% for lang, file in translations.items() %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center translation-item" data-lang="{{ lang }}">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-file-text"></i>
|
||||
<div>
|
||||
<strong>{{ lang.upper() }}</strong>
|
||||
<small class="text-muted d-block">{{ file }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="TranslationEditor.load('{{ lang }}')" title="{{ t('ui.actions.edit', 'Edit') }}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
{% if lang != 'en' %}
|
||||
<button type="button" class="btn btn-outline-danger" onclick="TranslationEditor.delete('{{ lang }}')" title="{{ t('ui.actions.delete', 'Delete') }}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-footer">
|
||||
<form action="/translations" method="post" class="d-flex gap-2">
|
||||
<select name="lang" class="form-select form-select-sm" data-choices data-placeholder="{{ t('ui.translations.add_language_placeholder', 'Add language...') }}" required>
|
||||
<option value="">{{ t('ui.translations.select_language_placeholder', 'Select language...') }}</option>
|
||||
{% for lang in metadata.suggestions.languages %}
|
||||
{% if lang not in translations %}
|
||||
<option value="{{ lang }}">{{ lang }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-plus-lg"></i> {{ t('ui.actions.add', 'Add') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,30 @@
|
||||
{# Workflow templates card macro. #}
|
||||
{% macro workflow_templates() -%}
|
||||
<div class="amb-card mb-3">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-collection"></i> {{ t('ui.workflow.templates.title', 'Workflow Templates') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-lg-5">
|
||||
<label class="form-label small text-muted" for="workflow-template-select">
|
||||
{{ t('ui.workflow.templates.select_label', 'Template') }}
|
||||
</label>
|
||||
<select id="workflow-template-select" class="form-select form-select-sm">
|
||||
<option value="">{{ t('ui.workflow.templates.select_placeholder', 'Select a template...') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div id="workflow-template-description" class="small text-muted">
|
||||
{{ t('ui.workflow.templates.description_placeholder', 'Choose a template to preview what it does.') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 text-lg-end">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="workflow-template-apply" disabled>
|
||||
<i class="bi bi-box-arrow-in-right"></i> {{ t('ui.workflow.templates.apply', 'Apply Template') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
@@ -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() -%}
|
||||
<section id="prompt" class="amb-section">
|
||||
@@ -10,89 +9,11 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
{% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %}
|
||||
<form action="/prompt" method="post" id="prompt-form">
|
||||
<input type="hidden" name="prompt_mode" id="prompt-mode" value="builder">
|
||||
<div id="prompt-builder">
|
||||
{% 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
|
||||
) }}
|
||||
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t('ui.prompt.model.label', 'Choose a model') }}</label>
|
||||
{{ helper_text(t('ui.prompt.model.desc', 'Pick the balance of quality and speed that fits the task.')) }}
|
||||
<select name="model" class="form-select" data-choices>
|
||||
<option value="openai/gpt-4o" {% if 'gpt-4o' in prompt_content %}selected{% endif %}>GPT-4o ({{ t('ui.prompt.model.recommended', 'Recommended') }})</option>
|
||||
<option value="openai/gpt-4o-mini" {% if 'gpt-4o-mini' in prompt_content %}selected{% endif %}>GPT-4o Mini ({{ t('ui.prompt.model.faster', 'Faster') }})</option>
|
||||
<option value="openai/gpt-4-turbo" {% if 'gpt-4-turbo' in prompt_content %}selected{% endif %}>GPT-4 Turbo</option>
|
||||
<option value="anthropic/claude-3-5-sonnet" {% if 'claude' in prompt_content %}selected{% endif %}>Claude 3.5 Sonnet</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="prompt-raw" class="d-none">
|
||||
<label class="amb-form-label">{{ t('ui.prompt.raw.label', 'Advanced YAML') }}</label>
|
||||
{{ helper_text(t('ui.prompt.raw.desc', 'Edit the full YAML only if you need fine control.')) }}
|
||||
<textarea name="content" class="amb-code-editor" id="prompt-yaml" rows="18">{{ prompt_content }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="submit" class="btn btn-success" onclick="buildPromptYaml()">
|
||||
<i class="bi bi-save"></i> {{ t('ui.prompt.save', 'Save Prompt') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="toggleRawPrompt()">
|
||||
<i class="bi bi-code-slash"></i> {{ t('ui.prompt.advanced_yaml', 'Advanced YAML') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endcall %}
|
||||
{{ prompt_builder() }}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
{% call card(t('ui.prompt.guidance.title', 'Guidance'), "lightbulb") %}
|
||||
<div class="amb-guidance">
|
||||
<div class="mb-3">
|
||||
<strong>{{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<strong>{{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }}</strong>
|
||||
<p class="small text-muted mb-0">{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{{ prompt_guidance() }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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() -%}
|
||||
<section id="settings" class="amb-section">
|
||||
@@ -10,138 +14,17 @@
|
||||
|
||||
<form action="/settings" method="post" id="settings-form">
|
||||
<div class="row">
|
||||
{% set settings_desc = metadata.get('settings_descriptions', {}) %}
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-key"></i> {{ t('ui.settings.api_keys', 'API Keys') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">
|
||||
{{ t(desc_label, desc_label) }}
|
||||
{% if desc.get('required') %}<span class="amb-required">*</span>{% endif %}
|
||||
</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, '') }}"
|
||||
placeholder="{{ t(desc_placeholder, desc_placeholder) }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ settings_api_keys() }}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-gear"></i> {{ t('ui.settings.configuration', 'Configuration') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
{% if desc.get('type') == 'select' %}
|
||||
<select name="env_{{ key }}" class="form-select" data-choices>
|
||||
<option value="">{{ t('ui.common.select_placeholder', 'Select...') }}</option>
|
||||
{% for opt in desc.get('options', []) %}
|
||||
<option value="{{ opt }}" {% if env_vars.get(key) == opt %}selected{% endif %}>{{ opt }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, desc.get('default', '')) }}"
|
||||
placeholder="{{ t(desc_placeholder, desc_placeholder) }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-shield-lock"></i> {{ t('ui.settings.web_access', 'Web UI Access') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
{% 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', '') %}
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
{% if desc_text %}
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
{% endif %}
|
||||
<input type="{{ desc.get('type', 'text') }}"
|
||||
name="env_{{ key }}"
|
||||
class="form-control"
|
||||
value="{{ env_vars.get(key, '') }}">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ settings_configuration() }}
|
||||
{{ settings_web_access() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-sliders"></i> {{ t('ui.settings.other', 'Other Settings') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
<div class="row">
|
||||
{% 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 %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="amb-form-group">
|
||||
{% set desc = settings_desc.get(key, {}) %}
|
||||
{% set desc_label = desc.get('label', key) %}
|
||||
{% set desc_text = desc.get('description', t('ui.settings.custom_default_desc', 'Custom environment setting. Add a description in metadata.json to show it here.')) %}
|
||||
<label class="amb-form-label">{{ t(desc_label, desc_label) }}</label>
|
||||
<p class="text-muted small mb-2">{{ t(desc_text, desc_text) }}</p>
|
||||
<input type="text" name="env_{{ key }}" class="form-control" value="{{ value }}"
|
||||
placeholder="{{ t(desc.get('placeholder', ''), desc.get('placeholder', '')) }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="amb-form-group">
|
||||
<label class="amb-form-label">{{ t('ui.settings.add.title', 'Add New Setting') }}</label>
|
||||
<p class="text-muted small mb-2">{{ t('ui.settings.add.desc', 'Use uppercase keys with underscores, like API_TIMEOUT.') }}</p>
|
||||
<div class="input-group">
|
||||
<input type="text" name="new_env_key" class="form-control" placeholder="{{ t('ui.settings.add.placeholder_key', 'KEY') }}">
|
||||
<input type="text" name="new_env_value" class="form-control" placeholder="{{ t('ui.settings.add.placeholder_value', 'Value') }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-footer">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="bi bi-save"></i> {{ t('ui.settings.save_all', 'Save All Settings') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ settings_other() }}
|
||||
</form>
|
||||
</section>
|
||||
{%- endmacro %}
|
||||
|
||||
@@ -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() -%}
|
||||
<section id="translations" class="amb-section">
|
||||
@@ -8,111 +9,11 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-globe"></i> {{ t('ui.translations.languages', 'Languages') }}</h5>
|
||||
</div>
|
||||
<div class="amb-card-body p-0">
|
||||
<div class="list-group list-group-flush" id="translation-list">
|
||||
{% for lang, file in translations.items() %}
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center translation-item" data-lang="{{ lang }}">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<i class="bi bi-file-text"></i>
|
||||
<div>
|
||||
<strong>{{ lang.upper() }}</strong>
|
||||
<small class="text-muted d-block">{{ file }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="TranslationEditor.load('{{ lang }}')" title="{{ t('ui.actions.edit', 'Edit') }}">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
{% if lang != 'en' %}
|
||||
<button type="button" class="btn btn-outline-danger" onclick="TranslationEditor.delete('{{ lang }}')" title="{{ t('ui.actions.delete', 'Delete') }}">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-footer">
|
||||
<form action="/translations" method="post" class="d-flex gap-2">
|
||||
<select name="lang" class="form-select form-select-sm" data-choices data-placeholder="{{ t('ui.translations.add_language_placeholder', 'Add language...') }}" required>
|
||||
<option value="">{{ t('ui.translations.select_language_placeholder', 'Select language...') }}</option>
|
||||
{% for lang in metadata.suggestions.languages %}
|
||||
{% if lang not in translations %}
|
||||
<option value="{{ lang }}">{{ lang }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-plus-lg"></i> {{ t('ui.actions.add', 'Add') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{ translations_languages() }}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-pencil-square"></i> <span id="editor-title">{{ t('ui.translations.editor.title', 'Translation Editor') }}</span></h5>
|
||||
<div id="editor-actions" class="amb-translation-actions" style="display: none;">
|
||||
<span id="missing-count" class="amb-pill amb-pill-warning">{{ t('ui.translations.missing_count', '{count} missing') | replace('{count}', '0') }}</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.fillMissing()">
|
||||
<i class="bi bi-magic"></i> {{ t('ui.translations.actions.fill_missing', 'Fill Missing') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.reset()">
|
||||
<i class="bi bi-arrow-counterclockwise"></i> {{ t('ui.actions.reset', 'Reset') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="TranslationEditor.save()">
|
||||
<i class="bi bi-save"></i> {{ t('ui.actions.save', 'Save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="amb-card-body">
|
||||
<div id="translation-editor-placeholder">
|
||||
{{ 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.')) }}
|
||||
</div>
|
||||
<div id="translation-editor" style="display: none;">
|
||||
<div class="amb-translation-toolbar">
|
||||
<div class="amb-translation-search">
|
||||
<input type="search" class="form-control form-control-sm" id="translation-search"
|
||||
placeholder="{{ t('ui.translations.search.placeholder', 'Search keys or text...') }}" oninput="TranslationEditor.filter(this.value)">
|
||||
</div>
|
||||
<div class="form-check form-switch amb-translation-toggle">
|
||||
<input class="form-check-input" type="checkbox" id="translation-missing-toggle" checked
|
||||
onchange="TranslationEditor.toggleMissing(this.checked)">
|
||||
<label class="form-check-label small" for="translation-missing-toggle">{{ t('ui.translations.toggle_missing', 'Show missing keys') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="amb-translation-add">
|
||||
<input type="text" class="form-control form-control-sm" id="new-translation-key" placeholder="{{ t('ui.translations.add.key_placeholder', 'new.key') }}">
|
||||
<input type="text" class="form-control form-control-sm" id="new-translation-value" placeholder="{{ t('ui.translations.add.value_placeholder', 'Translation text') }}">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="TranslationEditor.prefillNewValue()">
|
||||
{{ t('ui.translations.add.use_english', 'Use English') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="TranslationEditor.addEntry()">
|
||||
<i class="bi bi-plus-lg"></i> {{ t('ui.translations.add.add_key', 'Add Key') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table table-sm" id="translation-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30%;">{{ t('ui.translations.table.key', 'Key') }}</th>
|
||||
<th>{{ t('ui.translations.table.translation', 'Translation') }}</th>
|
||||
<th style="width: 70px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ translations_editor() }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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 id="workflow" class="amb-section">
|
||||
{{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }}
|
||||
|
||||
{{ workflow_templates() }}
|
||||
|
||||
<div class="amb-card">
|
||||
<div class="amb-card-header">
|
||||
<h5><i class="bi bi-diagram-3"></i> {{ t('ui.workflow.card.title', 'Tasks & Steps') }}</h5>
|
||||
|
||||
11
src/autometabuilder/workflow_packages/blank.json
Normal file
11
src/autometabuilder/workflow_packages/blank.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"id": "blank",
|
||||
"label": "meta.workflow_packages.blank.label",
|
||||
"description": "meta.workflow_packages.blank.description",
|
||||
"tags": [
|
||||
"starter"
|
||||
],
|
||||
"workflow": {
|
||||
"nodes": []
|
||||
}
|
||||
}
|
||||
93
src/autometabuilder/workflow_packages/iterative_loop.json
Normal file
93
src/autometabuilder/workflow_packages/iterative_loop.json
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"id": "iterative_loop",
|
||||
"label": "meta.workflow_packages.iterative_loop.label",
|
||||
"description": "meta.workflow_packages.iterative_loop.description",
|
||||
"tags": [
|
||||
"loop",
|
||||
"tools"
|
||||
],
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "load_context",
|
||||
"type": "core.load_context",
|
||||
"outputs": {
|
||||
"context": "sdlc_context"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "seed_messages",
|
||||
"type": "core.seed_messages",
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_context",
|
||||
"type": "core.append_context_message",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"context": "$sdlc_context"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_user_instruction",
|
||||
"type": "core.append_user_instruction",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_loop",
|
||||
"type": "control.loop",
|
||||
"inputs": {
|
||||
"max_iterations": 10,
|
||||
"stop_when": "$no_tool_calls",
|
||||
"stop_on": "true"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"id": "ai_request",
|
||||
"type": "core.ai_request",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"response": "llm_response",
|
||||
"has_tool_calls": "has_tool_calls",
|
||||
"tool_calls_count": "tool_calls_count"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "run_tool_calls",
|
||||
"type": "core.run_tool_calls",
|
||||
"inputs": {
|
||||
"response": "$llm_response"
|
||||
},
|
||||
"outputs": {
|
||||
"tool_results": "tool_results",
|
||||
"no_tool_calls": "no_tool_calls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_tool_results",
|
||||
"type": "core.append_tool_results",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"tool_results": "$tool_results"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"id": "plan_execute_summarize",
|
||||
"label": "meta.workflow_packages.plan_execute_summarize.label",
|
||||
"description": "meta.workflow_packages.plan_execute_summarize.description",
|
||||
"tags": [
|
||||
"plan",
|
||||
"summarize"
|
||||
],
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "load_context",
|
||||
"type": "core.load_context",
|
||||
"outputs": {
|
||||
"context": "sdlc_context"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "seed_messages",
|
||||
"type": "core.seed_messages",
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_context",
|
||||
"type": "core.append_context_message",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"context": "$sdlc_context"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_user_instruction",
|
||||
"type": "core.append_user_instruction",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "planner_request",
|
||||
"type": "core.ai_request",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"response": "llm_response",
|
||||
"has_tool_calls": "has_tool_calls",
|
||||
"tool_calls_count": "tool_calls_count"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "run_tool_calls",
|
||||
"type": "core.run_tool_calls",
|
||||
"inputs": {
|
||||
"response": "$llm_response"
|
||||
},
|
||||
"outputs": {
|
||||
"tool_results": "tool_results",
|
||||
"no_tool_calls": "no_tool_calls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_tool_results",
|
||||
"type": "core.append_tool_results",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"tool_results": "$tool_results"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "summary_request",
|
||||
"type": "core.ai_request",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"response": "final_response",
|
||||
"has_tool_calls": "final_has_tool_calls",
|
||||
"tool_calls_count": "final_tool_calls_count"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
82
src/autometabuilder/workflow_packages/single_pass.json
Normal file
82
src/autometabuilder/workflow_packages/single_pass.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"id": "single_pass",
|
||||
"label": "meta.workflow_packages.single_pass.label",
|
||||
"description": "meta.workflow_packages.single_pass.description",
|
||||
"tags": [
|
||||
"single",
|
||||
"tools"
|
||||
],
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "load_context",
|
||||
"type": "core.load_context",
|
||||
"outputs": {
|
||||
"context": "sdlc_context"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "seed_messages",
|
||||
"type": "core.seed_messages",
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_context",
|
||||
"type": "core.append_context_message",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"context": "$sdlc_context"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_user_instruction",
|
||||
"type": "core.append_user_instruction",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ai_request",
|
||||
"type": "core.ai_request",
|
||||
"inputs": {
|
||||
"messages": "$messages"
|
||||
},
|
||||
"outputs": {
|
||||
"response": "llm_response",
|
||||
"has_tool_calls": "has_tool_calls",
|
||||
"tool_calls_count": "tool_calls_count"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "run_tool_calls",
|
||||
"type": "core.run_tool_calls",
|
||||
"inputs": {
|
||||
"response": "$llm_response"
|
||||
},
|
||||
"outputs": {
|
||||
"tool_results": "tool_results",
|
||||
"no_tool_calls": "no_tool_calls"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "append_tool_results",
|
||||
"type": "core.append_tool_results",
|
||||
"inputs": {
|
||||
"messages": "$messages",
|
||||
"tool_results": "$tool_results"
|
||||
},
|
||||
"outputs": {
|
||||
"messages": "messages"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -206,9 +206,11 @@ def test_workflow_builder_renders(page: Page, server: str):
|
||||
|
||||
# Wait for workflow builder to render
|
||||
page.wait_for_selector("#workflow-builder", state="attached")
|
||||
page.wait_for_selector("#workflow-template-select", state="attached")
|
||||
|
||||
# Should have at least the primary action button
|
||||
expect(page.locator("#workflow-builder .btn.btn-primary")).to_be_visible()
|
||||
page.wait_for_function("document.querySelectorAll('#workflow-template-select option').length > 1")
|
||||
|
||||
# Toggle raw JSON should work
|
||||
page.click(f"#workflow button:has-text('{t('ui.workflow.toggle_json')}')")
|
||||
|
||||
Reference in New Issue
Block a user