mirror of
https://github.com/johndoe6345789/AutoMetabuilder.git
synced 2026-04-24 13:54:59 +00:00
Implement Visual Workflow Builder: Introduce a web-based drag-and-drop interface for creating and managing workflows, enabling dynamic task and step management.
This commit is contained in:
@@ -95,14 +95,207 @@
|
||||
|
||||
<div class="row mt-4 mb-5">
|
||||
<div class="col-12">
|
||||
<h2>Workflow (workflow.json)</h2>
|
||||
<form action="/workflow" method="post">
|
||||
<textarea name="content" class="form-control" rows="15">{{ workflow_content }}</textarea>
|
||||
<h2>Workflow Builder (workflow.json)</h2>
|
||||
<div id="workflow-builder" class="mb-3">
|
||||
<!-- Visual builder will be rendered here -->
|
||||
</div>
|
||||
<form action="/workflow" method="post" id="workflow-form">
|
||||
<textarea id="workflow-content" name="content" class="form-control d-none" rows="15">{{ workflow_content }}</textarea>
|
||||
<button type="submit" class="btn btn-success mt-2">Save Workflow</button>
|
||||
<button type="button" class="btn btn-secondary mt-2" onclick="toggleRawWorkflow()">Toggle Raw JSON</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let workflow = [];
|
||||
try {
|
||||
workflow = JSON.parse(document.getElementById('workflow-content').value);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse workflow JSON", e);
|
||||
}
|
||||
|
||||
const stepDefinitions = {
|
||||
'load_context': ['output_key'],
|
||||
'prepare_messages': ['input_context', 'output_key'],
|
||||
'llm_gen': ['input_messages', 'output_key'],
|
||||
'process_response': ['input_response', 'output_key', 'stop_if_no_tools'],
|
||||
'update_messages': ['input_results', 'target_messages']
|
||||
};
|
||||
|
||||
function renderWorkflow() {
|
||||
const container = document.getElementById('workflow-builder');
|
||||
container.innerHTML = '';
|
||||
|
||||
workflow.forEach((task, taskIdx) => {
|
||||
const taskCard = document.createElement('div');
|
||||
taskCard.className = 'card mb-4 border-primary';
|
||||
taskCard.innerHTML = `
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<input type="text" class="form-control form-control-sm d-inline-block w-auto" value="${task.name || ''}" onchange="updateTask(${taskIdx}, 'name', this.value)" placeholder="Task Name">
|
||||
<select class="form-select form-select-sm d-inline-block w-auto ms-2" onchange="updateTask(${taskIdx}, 'type', this.value)">
|
||||
<option value="" ${!task.type ? 'selected' : ''}>Standard</option>
|
||||
<option value="loop" ${task.type === 'loop' ? 'selected' : ''}>Loop</option>
|
||||
</select>
|
||||
${task.type === 'loop' ? `<input type="number" class="form-control form-control-sm d-inline-block w-auto ms-2" value="${task.max_iterations || 1}" onchange="updateTask(${taskIdx}, 'max_iterations', parseInt(this.value))" title="Max Iterations">` : ''}
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-light" onclick="moveTask(${taskIdx}, -1)" ${taskIdx === 0 ? 'disabled' : ''}>↑</button>
|
||||
<button class="btn btn-sm btn-light" onclick="moveTask(${taskIdx}, 1)" ${taskIdx === workflow.length - 1 ? 'disabled' : ''}>↓</button>
|
||||
<button class="btn btn-sm btn-danger ms-2" onclick="removeTask(${taskIdx})">Remove Task</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="steps-${taskIdx}"></div>
|
||||
<button class="btn btn-sm btn-outline-primary mt-2" onclick="addStep(${taskIdx})">+ Add Step</button>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(taskCard);
|
||||
|
||||
const stepsContainer = taskCard.querySelector(`#steps-${taskIdx}`);
|
||||
(task.steps || []).forEach((step, stepIdx) => {
|
||||
const stepDiv = document.createElement('div');
|
||||
stepDiv.className = 'card mb-2 bg-light';
|
||||
|
||||
let fieldsHtml = `
|
||||
<select class="form-select form-select-sm mb-2" onchange="updateStepType(${taskIdx}, ${stepIdx}, this.value)">
|
||||
<option value="">Select Type</option>
|
||||
${Object.keys(stepDefinitions).map(type => `<option value="${type}" ${step.type === type ? 'selected' : ''}>${type}</option>`).join('')}
|
||||
</select>
|
||||
`;
|
||||
|
||||
if (step.type && stepDefinitions[step.type]) {
|
||||
fieldsHtml += '<div class="row g-2">';
|
||||
stepDefinitions[step.type].forEach(field => {
|
||||
const val = step[field] !== undefined ? step[field] : '';
|
||||
const inputType = typeof val === 'boolean' || field === 'stop_if_no_tools' ? 'checkbox' : 'text';
|
||||
|
||||
fieldsHtml += `
|
||||
<div class="col-md-4">
|
||||
<label class="small">${field}</label>
|
||||
${inputType === 'checkbox'
|
||||
? `<div class="form-check"><input type="checkbox" class="form-check-input" ${step[field] ? 'checked' : ''} onchange="updateStepField(${taskIdx}, ${stepIdx}, '${field}', this.checked)"></div>`
|
||||
: `<input type="text" class="form-control form-control-sm" value="${val}" onchange="updateStepField(${taskIdx}, ${stepIdx}, '${field}', this.value)">`
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
fieldsHtml += '</div>';
|
||||
}
|
||||
|
||||
stepDiv.innerHTML = `
|
||||
<div class="card-body p-2">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<span class="badge bg-secondary">Step ${stepIdx + 1}</span>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-link p-0 text-decoration-none" onclick="moveStep(${taskIdx}, ${stepIdx}, -1)" ${stepIdx === 0 ? 'disabled' : ''}>↑</button>
|
||||
<button class="btn btn-sm btn-link p-0 text-decoration-none ms-1" onclick="moveStep(${taskIdx}, ${stepIdx}, 1)" ${stepIdx === (task.steps.length - 1) ? 'disabled' : ''}>↓</button>
|
||||
<button class="btn btn-sm btn-link p-0 text-danger text-decoration-none ms-2" onclick="removeStep(${taskIdx}, ${stepIdx})">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
${fieldsHtml}
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(stepDiv);
|
||||
});
|
||||
});
|
||||
|
||||
const addTaskBtn = document.createElement('button');
|
||||
addTaskBtn.className = 'btn btn-primary mb-5';
|
||||
addTaskBtn.textContent = '+ Add Task';
|
||||
addTaskBtn.onclick = addTask;
|
||||
container.appendChild(addTaskBtn);
|
||||
|
||||
syncWorkflow();
|
||||
}
|
||||
|
||||
function syncWorkflow() {
|
||||
document.getElementById('workflow-content').value = JSON.stringify(workflow, null, 2);
|
||||
}
|
||||
|
||||
function toggleRawWorkflow() {
|
||||
const textarea = document.getElementById('workflow-content');
|
||||
textarea.classList.toggle('d-none');
|
||||
if (!textarea.classList.contains('d-none')) {
|
||||
textarea.oninput = function() {
|
||||
try {
|
||||
workflow = JSON.parse(this.value);
|
||||
renderWorkflow();
|
||||
} catch (e) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function updateTask(idx, field, value) {
|
||||
workflow[idx][field] = value;
|
||||
if (field === 'type' && value !== 'loop') {
|
||||
delete workflow[idx].max_iterations;
|
||||
} else if (field === 'type' && value === 'loop' && !workflow[idx].max_iterations) {
|
||||
workflow[idx].max_iterations = 10;
|
||||
}
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function addTask() {
|
||||
workflow.push({ name: 'New Task', steps: [] });
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function removeTask(idx) {
|
||||
if (confirm('Remove this task?')) {
|
||||
workflow.splice(idx, 1);
|
||||
renderWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
function moveTask(idx, dir) {
|
||||
const target = idx + dir;
|
||||
if (target >= 0 && target < workflow.length) {
|
||||
[workflow[idx], workflow[target]] = [workflow[target], workflow[idx]];
|
||||
renderWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
function updateStepType(taskIdx, stepIdx, type) {
|
||||
const oldStep = workflow[taskIdx].steps[stepIdx];
|
||||
const newStep = { type: type };
|
||||
if (stepDefinitions[type]) {
|
||||
stepDefinitions[type].forEach(field => {
|
||||
if (field === 'stop_if_no_tools') newStep[field] = false;
|
||||
else newStep[field] = '';
|
||||
});
|
||||
}
|
||||
workflow[taskIdx].steps[stepIdx] = newStep;
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function updateStepField(taskIdx, stepIdx, field, value) {
|
||||
workflow[taskIdx].steps[stepIdx][field] = value;
|
||||
syncWorkflow();
|
||||
}
|
||||
|
||||
function addStep(taskIdx) {
|
||||
if (!workflow[taskIdx].steps) workflow[taskIdx].steps = [];
|
||||
workflow[taskIdx].steps.push({ type: '' });
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function removeStep(taskIdx, stepIdx) {
|
||||
workflow[taskIdx].steps.splice(stepIdx, 1);
|
||||
renderWorkflow();
|
||||
}
|
||||
|
||||
function moveStep(taskIdx, stepIdx, dir) {
|
||||
const target = stepIdx + dir;
|
||||
const steps = workflow[taskIdx].steps;
|
||||
if (target >= 0 && target < steps.length) {
|
||||
[steps[stepIdx], steps[target]] = [steps[target], steps[stepIdx]];
|
||||
renderWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
renderWorkflow();
|
||||
|
||||
async function refreshLogs() {
|
||||
try {
|
||||
const response = await fetch('/api/logs');
|
||||
|
||||
Reference in New Issue
Block a user