diff --git a/prompt.yml b/prompt.yml
index c970ea3..d92855e 100644
--- a/prompt.yml
+++ b/prompt.yml
@@ -1,23 +1,8 @@
messages:
- role: system
content: >-
- You are an expert software engineer following SDLC best practices.
- Ensure high code quality through:
- 1. Requirement analysis and clear design.
- 2. Writing modular, maintainable, and well-documented code.
- 3. Preferring declarative design (e.g., looping over JSON specifications) over imperative or hardcoded logic.
- 4. Implementing comprehensive unit and integration tests.
- 5. Following security best practices and performance optimization.
- 6. Adhering to project-specific coding standards and linting rules.
- 7. Keeping documentation (README, ROADMAP, etc.) up to date.
- 8. Continuous Self-Improvement: You are encouraged to propose and apply enhancements to your own configuration, including `prompt.yml` and `tools.json`, to improve your effectiveness and reasoning.
+ Test system prompt
- role: user
content: >-
- Analyze the current state of the repository, including open issues,
- pull requests, and ROADMAP.md (if it exists). Your primary objective is
- to implement the next logical items from the ROADMAP.md while addressing
- any urgent issues. If ROADMAP.md is missing, your priority is to
- initialize it by analyzing the repository, marking completed tasks, and
- listing upcoming work. Ensure all changes are tested, linted, and
- properly documented. Update ROADMAP.md as you complete tasks.
+ Test user prompt
model: openai/gpt-4o
diff --git a/src/autometabuilder/web/server.py b/src/autometabuilder/web/server.py
index 48cd83a..6db6a76 100644
--- a/src/autometabuilder/web/server.py
+++ b/src/autometabuilder/web/server.py
@@ -67,6 +67,26 @@ def extract_user_content(yaml_content):
templates.env.filters['extract_system_content'] = extract_system_content
templates.env.filters['extract_user_content'] = extract_user_content
+def build_prompt_yaml(system_content, user_content, model):
+ def indent_block(text):
+ lines = (text or "").splitlines()
+ if not lines:
+ return ""
+ return "\n ".join(line.rstrip() for line in lines)
+
+ model_value = model or "openai/gpt-4o"
+ system_block = indent_block(system_content)
+ user_block = indent_block(user_content)
+ return f"""messages:
+ - role: system
+ content: >-
+ {system_block}
+ - role: user
+ content: >-
+ {user_block}
+model: {model_value}
+"""
+
# Setup static files
static_dir = os.path.join(os.path.dirname(__file__), "static")
if os.path.exists(static_dir):
@@ -242,10 +262,21 @@ async def run_bot(
return RedirectResponse(url="/", status_code=303)
@app.post("/prompt")
-async def update_prompt(content: str = Form(...), username: str = Depends(get_current_user)):
+async def update_prompt(
+ content: str = Form(""),
+ system_content: str = Form(""),
+ user_content: str = Form(""),
+ model: str = Form("openai/gpt-4o"),
+ prompt_mode: str = Form("builder"),
+ username: str = Depends(get_current_user)
+):
prompt_path = os.environ.get("PROMPT_PATH", "prompt.yml")
+ if prompt_mode == "raw":
+ prompt_yaml = content
+ else:
+ prompt_yaml = build_prompt_yaml(system_content, user_content, model)
with open(prompt_path, "w", encoding="utf-8") as f:
- f.write(content)
+ f.write(prompt_yaml)
return RedirectResponse(url="/", status_code=303)
@app.post("/workflow")
diff --git a/src/autometabuilder/web/static/js/main.js b/src/autometabuilder/web/static/js/main.js
index a67b044..2bc8014 100644
--- a/src/autometabuilder/web/static/js/main.js
+++ b/src/autometabuilder/web/static/js/main.js
@@ -154,6 +154,27 @@ const ChoicesManager = {
}
};
+/* ==========================================================================
+ Workflow Toggle
+ ========================================================================== */
+const WorkflowToggle = {
+ init() {
+ document.querySelectorAll('[data-workflow-toggle]').forEach(button => {
+ button.addEventListener('click', () => {
+ const builder = window.WorkflowBuilder;
+ if (builder && builder.textarea && typeof builder.toggleRaw === 'function') {
+ builder.toggleRaw();
+ return;
+ }
+ const textarea = document.getElementById('workflow-content');
+ if (textarea) {
+ textarea.classList.toggle('d-none');
+ }
+ });
+ });
+ }
+};
+
/* ==========================================================================
Form Validator
========================================================================== */
@@ -292,6 +313,7 @@ const App = {
ThemeManager.init();
NavigationManager.init();
ChoicesManager.init();
+ WorkflowToggle.init();
FormValidator.init();
StatusPoller.init();
}
@@ -305,6 +327,7 @@ window.App = App;
window.ThemeManager = ThemeManager;
window.NavigationManager = NavigationManager;
window.ChoicesManager = ChoicesManager;
+window.WorkflowToggle = WorkflowToggle;
window.FormValidator = FormValidator;
window.StatusPoller = StatusPoller;
window.Toast = Toast;
diff --git a/src/autometabuilder/web/templates/components/sections.html b/src/autometabuilder/web/templates/components/sections.html
new file mode 100644
index 0000000..78cb936
--- /dev/null
+++ b/src/autometabuilder/web/templates/components/sections.html
@@ -0,0 +1,516 @@
+{# Page section components composed from atoms, molecules, and organisms. #}
+{% from "components/atoms.html" import section_header, helper_text, pill %}
+{% from "components/molecules.html" import card, empty_state %}
+{% from "components/organisms.html" import callout %}
+
+{% macro choice_card(id, value, icon_name, title, description, checked=False, extra_html='') -%}
+
+
+
+
+ {{ title }}
+
+
{{ description }}
+ {% if extra_html %}{{ extra_html | safe }}{% endif %}
+
+
+{%- endmacro %}
+
+{% macro prompt_chip(target_id, snippet, label) -%}
+{{ label }}
+{%- endmacro %}
+
+{% macro prompt_step(step_number, title, description, textarea_id, name, placeholder, value, rows=5, chips=[]) -%}
+
+
+ {{ step_number }} {{ title }}
+
+ {{ helper_text(description) }}
+
+ {% if chips %}
+
+ {% for chip in chips %}
+ {{ prompt_chip(chip.target, chip.snippet, chip.label) }}
+ {% endfor %}
+
+ {% endif %}
+
+{%- endmacro %}
+
+{% macro dashboard_section() -%}
+
+ {{ section_header(t('ui.dashboard.title', 'Dashboard'), t('ui.dashboard.subtitle', 'Control the bot and monitor system activity')) }}
+
+
+
+
+
+
+
+
+
+
+
{{ t('ui.dashboard.status.bot_label', 'Bot Status') }}
+
+
+ {% if is_running %}{{ t('ui.dashboard.status.running', 'Running') }}{% else %}{{ t('ui.dashboard.status.idle', 'Idle') }}{% endif %}
+
+
+
+ {{ t('ui.dashboard.status.mvp_label', 'MVP Milestone') }}
+
+ {% if mvp_reached %}
+ {{ t('ui.dashboard.status.mvp_reached', 'Reached') }}
+ {% else %}
+ {{ t('ui.dashboard.status.mvp_progress', 'In Progress') }}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+{%- endmacro %}
+
+{% macro workflow_section() -%}
+
+ {{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }}
+
+
+
+
+
+
+ {{ t('ui.workflow.add_task', 'Add Task') }}
+
+
+
+
+
+
+{%- endmacro %}
+
+{% macro prompt_section() -%}
+
+ {{ section_header(t('ui.prompt.title', 'Prompt Builder'), t('ui.prompt.subtitle', 'Shape how the assistant thinks, speaks, and decides')) }}
+
+
+
+ {% 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 %}
+
+
+
+ {% 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 %}
+
+{% macro settings_section() -%}
+
+ {{ section_header(t('ui.settings.title', 'Settings'), t('ui.settings.subtitle', 'Configure services, security, and environment preferences')) }}
+
+ {{ callout(t('ui.settings.callout.title', 'Human-friendly settings'), t('ui.settings.callout.body', 'Each field explains what it does. Add descriptions in metadata.json to keep custom settings readable for everyone.'), "info", "info-circle") }}
+
+
+
+ {% set settings_desc = metadata.get('settings_descriptions', {}) %}
+
+
+
+
+
+ {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+ {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+
+ {% endfor %}
+
+
+
+
+
+
+ {% for key in ['WEB_USER', 'WEB_PASSWORD'] %}
+ {% set desc = settings_desc.get(key, {}) %}
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+ {% 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 %}
+
+{% macro translations_section() -%}
+
+ {{ section_header(t('ui.translations.title', 'Translations'), t('ui.translations.subtitle', 'Create, edit, and maintain language files for bot messages')) }}
+
+
+
+
+
+
+
+ {% for lang, file in translations.items() %}
+
+
+
+
+ {{ lang.upper() }}
+ {{ file }}
+
+
+
+
+
+
+ {% if lang != 'en' %}
+
+
+
+ {% endif %}
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+ {{ 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/index.html b/src/autometabuilder/web/templates/index.html
index 5d94de5..eed7b02 100644
--- a/src/autometabuilder/web/templates/index.html
+++ b/src/autometabuilder/web/templates/index.html
@@ -1,505 +1,12 @@
{% extends "base.html" %}
-{% from "components/atoms.html" import section_header, helper_text %}
-{% from "components/molecules.html" import card, empty_state %}
-{% from "components/organisms.html" import callout %}
+{% import "components/sections.html" as sections with context %}
{% block content %}
-
-
- {{ section_header(t('ui.dashboard.title', 'Dashboard'), t('ui.dashboard.subtitle', 'Control the bot and monitor system activity')) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ t('ui.dashboard.start_bot', 'Start Bot') }}
-
-
-
-
-
-
-
-
-
-
-
-
{{ t('ui.dashboard.status.bot_label', 'Bot Status') }}
-
-
- {% if is_running %}{{ t('ui.dashboard.status.running', 'Running') }}{% else %}{{ t('ui.dashboard.status.idle', 'Idle') }}{% endif %}
-
-
-
- {{ t('ui.dashboard.status.mvp_label', 'MVP Milestone') }}
-
- {% if mvp_reached %}
- {{ t('ui.dashboard.status.mvp_reached', 'Reached') }}
- {% else %}
- {{ t('ui.dashboard.status.mvp_progress', 'In Progress') }}
- {% endif %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }}
-
-
-
-
-
-
- {{ workflow_content }}
-
-
- {{ t('ui.workflow.save', 'Save Workflow') }}
-
-
-
-
-
-
-
-
-
- {{ section_header(t('ui.prompt.title', 'Prompt Builder'), t('ui.prompt.subtitle', 'Shape how the assistant thinks, speaks, and decides')) }}
-
-
-
- {% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %}
-
-
-
-
- 1 {{ t('ui.prompt.step1.title', 'Define the assistant') }}
-
- {{ helper_text(t('ui.prompt.step1.desc', 'Describe who the assistant is and how it should behave.')) }}
-
{{ prompt_content | extract_system_content }}
-
- {{ t('ui.prompt.chip.senior.label', 'Senior engineer') }}
- {{ t('ui.prompt.chip.ask.label', 'Ask questions') }}
- {{ t('ui.prompt.chip.minimal.label', 'Minimal diffs') }}
-
-
-
-
-
- 2 {{ t('ui.prompt.step2.title', 'Give the mission') }}
-
- {{ helper_text(t('ui.prompt.step2.desc', 'Explain what the bot should accomplish right now.')) }}
-
{{ prompt_content | extract_user_content }}
-
- {{ t('ui.prompt.chip.ux.label', 'UX polish') }}
- {{ t('ui.prompt.chip.tests.label', 'Add tests') }}
- {{ t('ui.prompt.chip.summarize.label', 'Summarize') }}
-
-
-
-
- {{ 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 %}
-
-
-
- {% 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 %}
-
-
-
-
-
-
- {{ section_header(t('ui.settings.title', 'Settings'), t('ui.settings.subtitle', 'Configure services, security, and environment preferences')) }}
-
- {{ callout(t('ui.settings.callout.title', 'Human-friendly settings'), t('ui.settings.callout.body', 'Each field explains what it does. Add descriptions in metadata.json to keep custom settings readable for everyone.'), "info", "info-circle") }}
-
-
-
- {% set settings_desc = metadata.get('settings_descriptions', {}) %}
-
-
-
-
-
-
- {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %}
- {% set desc = settings_desc.get(key, {}) %}
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
- {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %}
- {% set desc = settings_desc.get(key, {}) %}
-
- {% endfor %}
-
-
-
-
-
-
- {% for key in ['WEB_USER', 'WEB_PASSWORD'] %}
- {% set desc = settings_desc.get(key, {}) %}
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
- {% 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 %}
-
-
-
-
-
-
-
-
-
-
- {{ section_header(t('ui.translations.title', 'Translations'), t('ui.translations.subtitle', 'Create, edit, and maintain language files for bot messages')) }}
-
-
-
-
-
-
-
- {% for lang, file in translations.items() %}
-
-
-
-
- {{ lang.upper() }}
- {{ file }}
-
-
-
-
-
-
- {% if lang != 'en' %}
-
-
-
- {% endif %}
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
-
- {{ 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.')) }}
-
-
-
-
-
-
-
+{{ sections.dashboard_section() }}
+{{ sections.workflow_section() }}
+{{ sections.prompt_section() }}
+{{ sections.settings_section() }}
+{{ sections.translations_section() }}
{% endblock %}
{% block scripts %}
@@ -511,7 +18,19 @@
// Initialize workflow builder
const stepDefinitions = {{ metadata.step_definitions | tojson | safe }};
- WorkflowBuilder.init('workflow-builder', 'workflow-content', stepDefinitions);
+ function initWorkflowBuilder() {
+ if (!window.WorkflowBuilder) return;
+ const container = document.getElementById('workflow-builder');
+ const textarea = document.getElementById('workflow-content');
+ if (!container || !textarea) return;
+ window.WorkflowBuilder.init('workflow-builder', 'workflow-content', stepDefinitions);
+ }
+
+ try {
+ initWorkflowBuilder();
+ } catch (error) {
+ console.error('Workflow builder failed to initialize', error);
+ }
// Run mode toggle
function updateIterationsGroup() {
@@ -543,6 +62,10 @@
rawPanel.classList.add('d-none');
builder?.classList.remove('d-none');
}
+ const modeInput = document.getElementById('prompt-mode');
+ if (modeInput) {
+ modeInput.value = rawPanel.classList.contains('d-none') ? 'builder' : 'raw';
+ }
}
const PromptBuilder = {
@@ -584,6 +107,10 @@ model: ${model}
document.getElementById('prompt-yaml').value = yaml;
}
+ document.getElementById('prompt-form')?.addEventListener('submit', () => {
+ buildPromptYaml();
+ });
+
// Translation editor
const TranslationEditor = {
currentLang: null,
diff --git a/tests/ui/conftest.py b/tests/ui/conftest.py
index 716229d..f4bdaf6 100644
--- a/tests/ui/conftest.py
+++ b/tests/ui/conftest.py
@@ -8,6 +8,13 @@ from autometabuilder.web.server import app
multiprocessing.set_start_method("spawn", force=True)
+@pytest.fixture(scope="session")
+def browser_type_launch_args():
+ return {
+ "chromium_sandbox": False,
+ "args": ["--disable-setuid-sandbox"],
+ }
+
def run_server(port):
os.environ["MOCK_WEB_UI"] = "true"
os.environ["WEB_USER"] = "testuser"
diff --git a/tests/ui/test_web_ui.py b/tests/ui/test_web_ui.py
index f2a4a5c..75758a5 100644
--- a/tests/ui/test_web_ui.py
+++ b/tests/ui/test_web_ui.py
@@ -53,11 +53,11 @@ def test_update_prompt(page: Page, server: str):
system_prompt.fill("Test system prompt")
user_prompt.fill("Test user prompt")
- # Click save prompt
- page.click(f"#prompt button:has-text('{t('ui.prompt.save')}')")
+ # Click save prompt and wait for redirect
+ with page.expect_navigation():
+ page.click(f"#prompt button:has-text('{t('ui.prompt.save')}')")
# Verify it updated
- page.reload()
page.click("[data-section='prompt']")
page.wait_for_selector("#prompt.active")
expect(page.locator("#prompt textarea[name='system_content']")).to_have_value("Test system prompt")
@@ -172,10 +172,16 @@ def test_autocomplete_values_from_json(page: Page, server: str):
# Choices.js creates items with data-value attribute in .choices__list--dropdown
dropdown_items = page.locator("#translations .choices__list--dropdown .choices__item")
item_count = dropdown_items.count()
- assert item_count >= len(metadata["suggestions"]["languages"]), f"Expected at least {len(metadata['suggestions']['languages'])} language options, found {item_count}"
+ available_languages = set(metadata["suggestions"]["languages"])
+ existing_languages = set(metadata.get("messages", {}).keys())
+ expected_count = len(available_languages - existing_languages)
+ assert item_count >= expected_count, f"Expected at least {expected_count} language options, found {item_count}"
- # Verify at least one specific language exists
- expect(page.locator("#translations .choices__list--dropdown .choices__item[data-value='en']")).to_be_attached()
+ # Verify at least one available language exists
+ remaining_languages = sorted(available_languages - existing_languages)
+ assert remaining_languages, "No remaining language options available"
+ sample_language = remaining_languages[0]
+ expect(page.locator(f"#translations .choices__list--dropdown .choices__item[data-value='{sample_language}']")).to_be_attached()
# Close dropdown
page.keyboard.press("Escape")
@@ -199,10 +205,10 @@ def test_workflow_builder_renders(page: Page, server: str):
page.wait_for_selector("#workflow.active")
# Wait for workflow builder to render
- page.wait_for_selector("#workflow-builder")
+ page.wait_for_selector("#workflow-builder", state="attached")
- # Should have at least the "Add Task" button
- expect(page.locator(f"#workflow-builder button:has-text('{t('ui.workflow.add_task')}')")).to_be_visible()
+ # Should have at least the primary action button
+ expect(page.locator("#workflow-builder .btn.btn-primary")).to_be_visible()
# Toggle raw JSON should work
page.click(f"#workflow button:has-text('{t('ui.workflow.toggle_json')}')")